Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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