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
5 changes: 5 additions & 0 deletions ini.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ type LoadOptions struct {
// empty assignment to reset prior values. Empty values are otherwise
// omitted.
PreserveEmptyShadowValues bool
// EscapeSliceDelimiters indicates whether ReflectFrom escapes backslashes
// and the delimiter in the elements of a string slice, so Key.Strings and
// MapTo read back the same elements. Other INI readers may not unescape
// them, so it is off by default.
EscapeSliceDelimiters bool
}

// DebugFunc is the type of function called to log parse events.
Expand Down
10 changes: 10 additions & 0 deletions key.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,16 @@ func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
}

// escapeDelimited prepares a slice element for joining with delim.
// Backslashes are escaped first, then occurrences of delim, matching Strings().
func escapeDelimited(val, delim string) string {
if delim == "" {
return val
}
val = strings.ReplaceAll(val, `\`, `\\`)
return strings.ReplaceAll(val, delim, `\`+delim)
}

// Strings returns list of string divided by given delimiter.
func (k *Key) Strings(delim string) []string {
str := k.String()
Expand Down
6 changes: 5 additions & 1 deletion struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,11 @@ func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, all
for i := 0; i < field.Len(); i++ {
switch sliceOf {
case reflect.String:
buf.WriteString(slice.Index(i).String())
val := slice.Index(i).String()
if key.s.f.options.EscapeSliceDelimiters {
val = escapeDelimited(val, delim)
}
buf.WriteString(val)
case reflect.Int, reflect.Int64:
fmt.Fprint(&buf, slice.Index(i).Int())
case reflect.Uint, reflect.Uint64:
Expand Down
42 changes: 42 additions & 0 deletions struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,48 @@ func Test_Duration(t *testing.T) {
})
}

// https://github.com/go-ini/ini/issues/348
func Test_ReflectFromStringSliceWithComma(t *testing.T) {
type C struct {
Scale float32
History []string
}
in := C{
Scale: 1.5,
History: []string{
"Test One",
"Test Two",
"Test with comma, Three",
`C:\dir\`,
"Test Four",
},
}

t.Run("escaped with EscapeSliceDelimiters", func(t *testing.T) {
f := Empty(LoadOptions{EscapeSliceDelimiters: true})
require.NoError(t, f.ReflectFrom(&in))

var buf bytes.Buffer
_, err := f.WriteTo(&buf)
require.NoError(t, err)
assert.Contains(t, buf.String(), `Test with comma\, Three`)

got := f.Section("").Key("History").Strings(",")
assert.Equal(t, in.History, got)

var out C
require.NoError(t, MapTo(&out, buf.Bytes()))
assert.Equal(t, in.History, out.History)
assert.Equal(t, float32(1.5), out.Scale)
})

t.Run("written as is by default", func(t *testing.T) {
f := Empty()
require.NoError(t, f.ReflectFrom(&in))
assert.Equal(t, `Test One,Test Two,Test with comma, Three,C:\dir\,Test Four`, f.Section("").Key("History").String())
})
}

type Employer struct {
Name string
Title string
Expand Down