Skip to content

Commit

Permalink
Add extends functionality to allow embedded fields (#269)
Browse files Browse the repository at this point in the history
* Add extends functionality to allow embedded fields

Signed-off-by: Andrew Thornton <[email protected]>

* allow extends with name

Signed-off-by: Andrew Thornton <[email protected]>

* Update struct.go

Co-authored-by: ᴜɴᴋɴᴡᴏɴ <[email protected]>

* Update struct_test.go

Co-authored-by: ᴜɴᴋɴᴡᴏɴ <[email protected]>
  • Loading branch information
zeripath and unknwon authored Oct 6, 2020
1 parent 2a76f61 commit 5e97220
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 35 deletions.
58 changes: 38 additions & 20 deletions struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,24 +263,21 @@ 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"
for _, opt := range opts[1:] {
omitEmpty = omitEmpty || (opt == "omitempty")
allowShadow = allowShadow || (opt == "allowshadow")
allowNonUnique = allowNonUnique || (opt == "nonunique")
extends = extends || (opt == "extends")
}
if len(opts) > 2 {
allowShadow = opts[2] == "allowshadow"
}
if len(opts) > 3 {
allowNonUnique = opts[3] == "nonunique"
}
return rawName, omitEmpty, allowShadow, allowNonUnique
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()
}
Expand All @@ -295,20 +292,34 @@ 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
}

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()))
}
fieldSection := s
if rawName != "" {
sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
if secs, err := s.f.SectionsByName(sectionName); err == nil && 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 {
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)
Expand All @@ -318,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
Expand Down Expand Up @@ -357,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)
}

Expand Down Expand Up @@ -387,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.
Expand Down Expand Up @@ -581,7 +592,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
}
Expand All @@ -595,6 +606,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.
Expand Down
53 changes: 38 additions & 15 deletions struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -90,7 +90,16 @@ type testPeer struct {

type testNonUniqueSectionsStruct struct {
Interface testInterface
Peer []testPeer `ini:",,,nonunique"`
Peer []testPeer `ini:",nonunique"`
}

type BaseStruct struct {
Base bool
}

type testExtend struct {
BaseStruct `ini:",extends"`
Extend bool
}

const confDataStruct = `
Expand Down Expand Up @@ -141,6 +150,10 @@ GPA = 2.8
[foo.bar]
Here = there
When = then
[extended]
Base = true
Extend = true
`

const confNonUniqueSectionDataStruct = `[Interface]
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -443,7 +466,7 @@ FieldInSection = 6
}

type File struct {
Sections []Section `ini:"Section,,,nonunique"`
Sections []Section `ini:"Section,nonunique"`
}

f := new(File)
Expand Down Expand Up @@ -735,18 +758,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{
Expand Down

0 comments on commit 5e97220

Please sign in to comment.