From 53869fa913d6aa10b2cd36ff74e5842a97a0d816 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Thu, 24 Sep 2020 21:45:40 +0100 Subject: [PATCH 1/4] Add extends functionality to allow embedded fields Signed-off-by: Andrew Thornton --- struct.go | 33 +++++++++++++++++++++++++-------- struct_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/struct.go b/struct.go index ad90300..5bf79b1 100644 --- a/struct.go +++ b/struct.go @@ -263,8 +263,8 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri return nil } -func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool) { - opts := strings.SplitN(tag, ",", 4) +func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) { + opts := strings.SplitN(tag, ",", 5) rawName = opts[0] if len(opts) > 1 { omitEmpty = opts[1] == "omitempty" @@ -275,7 +275,10 @@ func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bo if len(opts) > 3 { allowNonUnique = opts[3] == "nonunique" } - return rawName, omitEmpty, allowShadow, allowNonUnique + if len(opts) > 4 { + extends = opts[4] == "extends" + } + return rawName, omitEmpty, allowShadow, allowNonUnique, extends } // mapToField maps the given value to the matching field of the given section. @@ -295,7 +298,7 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int) continue } - rawName, _, allowShadow, allowNonUnique := parseTagOptions(tag) + rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag) fieldName := s.parseFieldName(tpField.Name, rawName) if len(fieldName) == 0 || !field.CanSet() { continue @@ -303,12 +306,19 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int) isStruct := tpField.Type.Kind() == reflect.Struct isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct - isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous - if isAnonymous { + isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous + if isAnonymousPtr { field.Set(reflect.New(tpField.Type.Elem())) } - if isAnonymous || isStruct || isStructPtr { + if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) { + if isStructPtr && field.IsNil() { + field.Set(reflect.New(tpField.Type.Elem())) + } + if err := s.mapToField(field, isStrict, sectionIndex); err != nil { + return fmt.Errorf("map to field %q: %v", fieldName, err) + } + } else if isAnonymousPtr || isStruct || isStructPtr { if secs, err := s.f.SectionsByName(fieldName); err == nil { if len(secs) <= sectionIndex { return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName) @@ -581,7 +591,7 @@ func (s *Section) reflectFrom(val reflect.Value) error { continue } - rawName, omitEmpty, allowShadow, allowNonUnique := parseTagOptions(tag) + rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag) if omitEmpty && isEmptyValue(field) { continue } @@ -595,6 +605,13 @@ func (s *Section) reflectFrom(val reflect.Value) error { continue } + if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) { + if err := s.reflectFrom(field); err != nil { + return fmt.Errorf("reflect from field %q: %v", fieldName, err) + } + continue + } + if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) || (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") { // Note: The only error here is section doesn't exist. diff --git a/struct_test.go b/struct_test.go index 4a0d579..50dcc76 100644 --- a/struct_test.go +++ b/struct_test.go @@ -93,6 +93,15 @@ type testNonUniqueSectionsStruct struct { Peer []testPeer `ini:",,,nonunique"` } +type BaseStruct struct { + Base bool +} + +type testExtend struct { + BaseStruct `ini:",,,,extends"` + Extend bool +} + const confDataStruct = ` NAME = Unknwon Age = 21 @@ -141,6 +150,10 @@ GPA = 2.8 [foo.bar] Here = there When = then + +[extended] +Base = true +Extend = true ` const confNonUniqueSectionDataStruct = `[Interface] @@ -332,6 +345,16 @@ func Test_MapToStruct(t *testing.T) { So(dv.Born.String(), ShouldEqual, t.String()) So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston") }) + + Convey("Map to extended base", func() { + f, err := ini.Load([]byte(confDataStruct)) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + te := testExtend{} + So(f.Section("extended").MapTo(&te), ShouldBeNil) + So(te.Base, ShouldBeTrue) + So(te.Extend, ShouldBeTrue) + }) }) Convey("Map to struct in strict mode", t, func() { @@ -364,6 +387,18 @@ names=alice, bruce`)) }) } +func Test_MapToExtended(t *testing.T) { + Convey("Map to extended base", t, func() { + f, err := ini.Load([]byte(confDataStruct)) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + te := testExtend{} + So(f.Section("extended").MapTo(&te), ShouldBeNil) + So(te.Base, ShouldBeTrue) + So(te.Extend, ShouldBeTrue) + }) +} + func Test_MapToStructNonUniqueSections(t *testing.T) { Convey("Map to struct non unique", t, func() { Convey("Map file to struct non unique", func() { From 61851be8890d822481d6b38afef89a6924c9b148 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sat, 3 Oct 2020 11:11:02 +0100 Subject: [PATCH 2/4] allow extends with name Signed-off-by: Andrew Thornton --- struct.go | 33 +++++++++++++++++---------------- struct_test.go | 32 ++++++++++++++++---------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/struct.go b/struct.go index 5bf79b1..fa30318 100644 --- a/struct.go +++ b/struct.go @@ -266,24 +266,18 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) { opts := strings.SplitN(tag, ",", 5) rawName = opts[0] - if len(opts) > 1 { - omitEmpty = opts[1] == "omitempty" - } - if len(opts) > 2 { - allowShadow = opts[2] == "allowshadow" - } - if len(opts) > 3 { - allowNonUnique = opts[3] == "nonunique" - } - if len(opts) > 4 { - extends = opts[4] == "extends" + for _, opt := range opts[1:] { + omitEmpty = omitEmpty || (opt == "omitempty") + allowShadow = allowShadow || (opt == "allowshadow") + allowNonUnique = allowNonUnique || (opt == "nonunique") + extends = extends || (opt == "extends") } return rawName, omitEmpty, allowShadow, allowNonUnique, extends } // mapToField maps the given value to the matching field of the given section. // The sectionIndex is the index (if non unique sections are enabled) to which the value should be added. -func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int) error { +func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error { if val.Kind() == reflect.Ptr { val = val.Elem() } @@ -315,7 +309,14 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int) if isStructPtr && field.IsNil() { field.Set(reflect.New(tpField.Type.Elem())) } - if err := s.mapToField(field, isStrict, sectionIndex); err != nil { + fieldSection := s + if rawName != "" { + sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName + if secs, err := s.f.SectionsByName(s.name + s.f.options.ChildSectionDelimiter + rawName); err == nil && len(secs) > 0 && sectionIndex < len(secs) { + fieldSection = secs[sectionIndex] + } + } + if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil { return fmt.Errorf("map to field %q: %v", fieldName, err) } } else if isAnonymousPtr || isStruct || isStructPtr { @@ -328,7 +329,7 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int) if isStructPtr && field.IsNil() { field.Set(reflect.New(tpField.Type.Elem())) } - if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex); err != nil { + if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil { return fmt.Errorf("map to field %q: %v", fieldName, err) } continue @@ -367,7 +368,7 @@ func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) ( typ := val.Type().Elem() for i, sec := range secs { elem := reflect.New(typ) - if err = sec.mapToField(elem, isStrict, i); err != nil { + if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil { return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err) } @@ -397,7 +398,7 @@ func (s *Section) mapTo(v interface{}, isStrict bool) error { return nil } - return s.mapToField(val, isStrict, 0) + return s.mapToField(val, isStrict, 0, s.name) } // MapTo maps section to given struct. diff --git a/struct_test.go b/struct_test.go index 50dcc76..83c1182 100644 --- a/struct_test.go +++ b/struct_test.go @@ -58,8 +58,8 @@ type testStruct struct { Unused int `ini:"-"` Unsigned uint Omitted bool `ini:"omitthis,omitempty"` - Shadows []string `ini:",,allowshadow"` - ShadowInts []int `ini:"Shadows,,allowshadow"` + Shadows []string `ini:",allowshadow"` + ShadowInts []int `ini:"Shadows,allowshadow"` BoolPtr *bool BoolPtrNil *bool FloatPtr *float64 @@ -90,7 +90,7 @@ type testPeer struct { type testNonUniqueSectionsStruct struct { Interface testInterface - Peer []testPeer `ini:",,,nonunique"` + Peer []testPeer `ini:",nonunique"` } type BaseStruct struct { @@ -98,7 +98,7 @@ type BaseStruct struct { } type testExtend struct { - BaseStruct `ini:",,,,extends"` + BaseStruct `ini:",extends"` Extend bool } @@ -478,7 +478,7 @@ FieldInSection = 6 } type File struct { - Sections []Section `ini:"Section,,,nonunique"` + Sections []Section `ini:"Section,nonunique"` } f := new(File) @@ -770,18 +770,18 @@ path = /tmp/gpm-profiles/test1.profile AllowShadows: true, }) type ShadowStruct struct { - StringArray []string `ini:"sa,,allowshadow"` + StringArray []string `ini:"sa,allowshadow"` EmptyStringArrat []string `ini:"empty,omitempty,allowshadow"` - Allowshadow []string `ini:"allowshadow,,allowshadow"` - Dates []time.Time `ini:",,allowshadow"` - Places []string `ini:",,allowshadow"` - Years []int `ini:",,allowshadow"` - Numbers []int64 `ini:",,allowshadow"` - Ages []uint `ini:",,allowshadow"` - Populations []uint64 `ini:",,allowshadow"` - Coordinates []float64 `ini:",,allowshadow"` - Flags []bool `ini:",,allowshadow"` - None []int `ini:",,allowshadow"` + Allowshadow []string `ini:"allowshadow,allowshadow"` + Dates []time.Time `ini:",allowshadow"` + Places []string `ini:",allowshadow"` + Years []int `ini:",allowshadow"` + Numbers []int64 `ini:",allowshadow"` + Ages []uint `ini:",allowshadow"` + Populations []uint64 `ini:",allowshadow"` + Coordinates []float64 `ini:",allowshadow"` + Flags []bool `ini:",allowshadow"` + None []int `ini:",allowshadow"` } shadow := &ShadowStruct{ From 4ac48892fae7862b7195d05918ab942ff4583d31 Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 6 Oct 2020 08:17:14 +0100 Subject: [PATCH 3/4] Update struct.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ᴜɴᴋɴᴡᴏɴ --- struct.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/struct.go b/struct.go index fa30318..a486b2f 100644 --- a/struct.go +++ b/struct.go @@ -312,7 +312,7 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, fieldSection := s if rawName != "" { sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName - if secs, err := s.f.SectionsByName(s.name + s.f.options.ChildSectionDelimiter + rawName); err == nil && len(secs) > 0 && sectionIndex < len(secs) { + if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) { fieldSection = secs[sectionIndex] } } From f83c0a3b423e16bb590e9b2def10b5a1e3f5b3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=9C=C9=B4=E1=B4=8B=C9=B4=E1=B4=A1=E1=B4=8F=C9=B4?= Date: Tue, 6 Oct 2020 15:25:52 +0800 Subject: [PATCH 4/4] Update struct_test.go --- struct_test.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/struct_test.go b/struct_test.go index 83c1182..65e6925 100644 --- a/struct_test.go +++ b/struct_test.go @@ -387,18 +387,6 @@ names=alice, bruce`)) }) } -func Test_MapToExtended(t *testing.T) { - Convey("Map to extended base", t, func() { - f, err := ini.Load([]byte(confDataStruct)) - So(err, ShouldBeNil) - So(f, ShouldNotBeNil) - te := testExtend{} - So(f.Section("extended").MapTo(&te), ShouldBeNil) - So(te.Base, ShouldBeTrue) - So(te.Extend, ShouldBeTrue) - }) -} - func Test_MapToStructNonUniqueSections(t *testing.T) { Convey("Map to struct non unique", t, func() { Convey("Map file to struct non unique", func() {