mirror of
https://github.com/go-ini/ini.git
synced 2026-06-19 21:46:45 +02:00
Add extends functionality to allow embedded fields (#269)
* Add extends functionality to allow embedded fields Signed-off-by: Andrew Thornton <art27@cantab.net> * allow extends with name Signed-off-by: Andrew Thornton <art27@cantab.net> * Update struct.go Co-authored-by: ᴜɴᴋɴᴡᴏɴ <u@gogs.io> * Update struct_test.go Co-authored-by: ᴜɴᴋɴᴡᴏɴ <u@gogs.io>
This commit is contained in:
@@ -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()
|
||||
}
|
||||
@@ -295,7 +292,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 +300,26 @@ 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()))
|
||||
}
|
||||
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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
+38
-15
@@ -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,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 = `
|
||||
@@ -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() {
|
||||
@@ -443,7 +466,7 @@ FieldInSection = 6
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Sections []Section `ini:"Section,,,nonunique"`
|
||||
Sections []Section `ini:"Section,nonunique"`
|
||||
}
|
||||
|
||||
f := new(File)
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user