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() {