do not loose read in quotes
diff --git a/ini_private.go b/ini_private.go
index 93658d6..3a44bd6 100644
--- a/ini_private.go
+++ b/ini_private.go
@@ -14,6 +14,7 @@
type iniValue struct {
Name string
Value string
+ Quoted bool
LineNumber uint
}
@@ -115,19 +116,19 @@
kind = val.Type().Elem().Kind()
if val.Len() == 0 {
- writeOption(writer, oname, kind, "", "", true)
+ writeOption(writer, oname, kind, "", "", true, option.iniQuote)
} else {
for idx := 0; idx < val.Len(); idx++ {
v, _ := convertToString(val.Index(idx), option.tag)
- writeOption(writer, oname, kind, "", v, commentOption)
+ writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
}
}
case reflect.Map:
kind = val.Type().Elem().Kind()
if val.Len() == 0 {
- writeOption(writer, oname, kind, "", "", true)
+ writeOption(writer, oname, kind, "", "", true, option.iniQuote)
} else {
mkeys := val.MapKeys()
keys := make([]string, len(val.MapKeys()))
@@ -143,13 +144,13 @@
for _, k := range keys {
v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag)
- writeOption(writer, oname, kind, k, v, commentOption)
+ writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote)
}
}
default:
v, _ := convertToString(val, option.tag)
- writeOption(writer, oname, kind, "", v, commentOption)
+ writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
}
if comments {
@@ -162,9 +163,9 @@
}
}
-func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool) {
- if optionType == reflect.String {
- optionValue = quoteIfNeeded(optionValue)
+func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) {
+ if forceQuote || (optionType == reflect.String && !strconv.CanBackquote(optionValue)) {
+ optionValue = strconv.Quote(optionValue)
}
comment := ""
@@ -307,10 +308,13 @@
name := strings.TrimSpace(keyval[0])
value := strings.TrimSpace(keyval[1])
+ quoted := false
if len(value) != 0 && value[0] == '"' {
if v, err := strconv.Unquote(value); err == nil {
value = v
+
+ quoted = true
} else {
return nil, &IniError{
Message: err.Error(),
@@ -323,6 +327,7 @@
section = append(section, iniValue{
Name: name,
Value: value,
+ Quoted: quoted,
LineNumber: lineno,
})
@@ -406,6 +411,8 @@
if len(parts) == 2 && parts[1][0] == '"' {
if v, err := strconv.Unquote(parts[1]); err == nil {
parts[1] = v
+
+ inival.Quoted = true
} else {
return &IniError{
Message: err.Error(),
@@ -429,6 +436,9 @@
}
}
+ // always quote in case one value of the option uses quotes
+ opt.iniQuote = opt.iniQuote || inival.Quoted
+
opt.tag.Set("_read-ini-name", inival.Name)
}
}
diff --git a/ini_test.go b/ini_test.go
index 888e56c..47afd37 100644
--- a/ini_test.go
+++ b/ini_test.go
@@ -299,6 +299,103 @@
}
}
+func TestReadAndWriteIni(t *testing.T) {
+ var tests = []struct {
+ options IniOptions
+ read string
+ write string
+ }{
+ {
+ IniIncludeComments,
+ `[Application Options]
+; Show verbose debug information
+verbose = true
+verbose = true
+
+; Test default value
+Default = "quote me"
+
+; Test default array value
+DefaultArray = 1
+DefaultArray = "2"
+DefaultArray = 3
+
+; Testdefault map value
+; DefaultMap =
+
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
+[Other Options]
+; A slice of strings
+; StringSlice =
+
+; A map from string to int
+int-map = a:2
+int-map = b:"3"
+
+`,
+ `[Application Options]
+; Show verbose debug information
+verbose = true
+verbose = true
+
+; Test default value
+Default = "quote me"
+
+; Test default array value
+DefaultArray = "1"
+DefaultArray = "2"
+DefaultArray = "3"
+
+; Testdefault map value
+; DefaultMap =
+
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
+[Other Options]
+; A slice of strings
+; StringSlice =
+
+; A map from string to int
+int-map = a:"2"
+int-map = b:"3"
+
+`,
+ },
+ }
+
+ for _, test := range tests {
+ var opts helpOptions
+
+ p := NewNamedParser("TestIni", Default)
+ p.AddGroup("Application Options", "The application options", &opts)
+
+ inip := NewIniParser(p)
+
+ read := strings.NewReader(test.read)
+ err := inip.Parse(read)
+ if err != nil {
+ t.Fatalf("Unexpected error: %s", err)
+ }
+
+ var write bytes.Buffer
+ inip.Write(&write, test.options)
+
+ got := write.String()
+
+ msg := fmt.Sprintf("with ini options %b", test.options)
+ assertDiff(t, got, test.write, msg)
+ }
+}
+
func TestReadIniWrongQuoting(t *testing.T) {
var tests = []struct {
iniFile string
diff --git a/option.go b/option.go
index 86bd030..29e702c 100644
--- a/option.go
+++ b/option.go
@@ -68,6 +68,9 @@
// The struct field value which the option represents.
value reflect.Value
+ // Determines if the option will be always quoted in the INI output
+ iniQuote bool
+
tag multiTag
isSet bool
}