Skip to content
Open
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
3 changes: 3 additions & 0 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ type File struct {

NameMapper
ValueMapper

// tagNameOverride, when set, replaces LoadOptions.TagName for a MapTo call.
tagNameOverride string
}

// newFile initializes File object with given data sources.
Expand Down
2 changes: 2 additions & 0 deletions ini.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ type LoadOptions struct {
AllowNonUniqueSections bool
// AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
AllowDuplicateShadowValues bool
// TagName is the struct tag used by MapTo and ReflectFrom. Empty means "ini".
TagName string
}

// DebugFunc is the type of function called to log parse events.
Expand Down
55 changes: 51 additions & 4 deletions struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,49 @@ func (s *Section) parseFieldName(raw, actual string) string {
return raw
}

func (f *File) structTagName() string {
if f.tagNameOverride != "" {
return f.tagNameOverride
}
if f.options.TagName != "" {
return f.options.TagName
}
return "ini"
}

func (s *Section) fieldTag(tpField reflect.StructField) string {
return tpField.Tag.Get(s.f.structTagName())
}

// MapToOption configures a MapTo or StrictMapTo call.
type MapToOption func(*mapToOptions)

type mapToOptions struct {
tagName string
}

// WithMapToOptionTag uses a custom struct tag name instead of "ini".
func WithMapToOptionTag(name string) MapToOption {
return func(o *mapToOptions) {
o.tagName = name
}
}

func (f *File) applyMapToOptions(opts []MapToOption) func() {
cfg := mapToOptions{}
for _, opt := range opts {
if opt != nil {
opt(&cfg)
}
}
if cfg.tagName == "" {
return func() {}
}
prev := f.tagNameOverride
f.tagNameOverride = cfg.tagName
return func() { f.tagNameOverride = prev }
}

func parseDelim(actual string) string {
if len(actual) > 0 {
return actual
Expand Down Expand Up @@ -287,7 +330,7 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int,
field := val.Field(i)
tpField := typ.Field(i)

tag := tpField.Tag.Get("ini")
tag := s.fieldTag(tpField)
if tag == "-" {
continue
}
Expand Down Expand Up @@ -413,13 +456,17 @@ func (s *Section) StrictMapTo(v interface{}) error {
}

// MapTo maps file to given struct.
func (f *File) MapTo(v interface{}) error {
func (f *File) MapTo(v interface{}, opts ...MapToOption) error {
restore := f.applyMapToOptions(opts)
defer restore()
return f.Section("").MapTo(v)
}

// StrictMapTo maps file to given struct in strict mode,
// which returns all possible error including value parsing error.
func (f *File) StrictMapTo(v interface{}) error {
func (f *File) StrictMapTo(v interface{}, opts ...MapToOption) error {
restore := f.applyMapToOptions(opts)
defer restore()
return f.Section("").StrictMapTo(v)
}

Expand Down Expand Up @@ -587,7 +634,7 @@ func (s *Section) reflectFrom(val reflect.Value) error {
field := val.Field(i)
tpField := typ.Field(i)

tag := tpField.Tag.Get("ini")
tag := s.fieldTag(tpField)
if tag == "-" {
continue
}
Expand Down
59 changes: 59 additions & 0 deletions struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -910,3 +910,62 @@ Title = Consultant Engineer
)
})
}

func TestMapTo_CustomTagName(t *testing.T) {
const raw = `
NAME = Unknwon
age = 21
`
cfg, err := Load([]byte(raw))
require.NoError(t, err)

type personJSON struct {
Name string `json:"NAME"`
Age int `json:"age"`
}
p := new(personJSON)
require.NoError(t, cfg.MapTo(p, WithMapToOptionTag("json")))
assert.Equal(t, "Unknwon", p.Name)
assert.Equal(t, 21, p.Age)

type personINI struct {
Name string `ini:"NAME"`
Age int `ini:"age"`
}
p2 := new(personINI)
require.NoError(t, cfg.MapTo(p2))
assert.Equal(t, "Unknwon", p2.Name)
assert.Equal(t, 21, p2.Age)
}

func TestMapTo_LoadOptionsTagName(t *testing.T) {
const raw = `
NAME = Unknwon
age = 21
`
cfg, err := LoadSources(LoadOptions{TagName: "json"}, []byte(raw))
require.NoError(t, err)

type personJSON struct {
Name string `json:"NAME"`
Age int `json:"age"`
}
p := new(personJSON)
require.NoError(t, cfg.MapTo(p))
assert.Equal(t, "Unknwon", p.Name)
assert.Equal(t, 21, p.Age)
}

func TestReflectFrom_LoadOptionsTagName(t *testing.T) {
type personJSON struct {
Name string `json:"NAME"`
Age int `json:"age"`
}
cfg := Empty(LoadOptions{TagName: "json"})
require.NoError(t, cfg.ReflectFrom(&personJSON{Name: "Unknwon", Age: 21}))

var buf bytes.Buffer
_, err := cfg.WriteTo(&buf)
require.NoError(t, err)
assert.Equal(t, "NAME = Unknwon\nage = 21\n", buf.String())
}