From 03a91ca08ab3d6c73e125fd8ed1b71b695d041a2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 31 Aug 2022 13:30:05 +0200 Subject: [PATCH] fix: Only use quotes if necessary in toIni template function --- pkg/cmd/templatefuncs.go | 29 +++++++- pkg/cmd/templatefuncs_test.go | 76 ++++++++++++++++---- pkg/cmd/testdata/scripts/templatefuncs.txtar | 4 +- 3 files changed, 92 insertions(+), 17 deletions(-) diff --git a/pkg/cmd/templatefuncs.go b/pkg/cmd/templatefuncs.go index ccb772a1084..bfd1f879bab 100644 --- a/pkg/cmd/templatefuncs.go +++ b/pkg/cmd/templatefuncs.go @@ -30,7 +30,12 @@ type ioregData struct { value map[string]any } -var startOfLineRx = regexp.MustCompile(`(?m)^`) +var ( + // needsQuoteRx matches any string that contains non-printable characters, + // double quotes, or a backslash. + needsQuoteRx = regexp.MustCompile(`[^\x21\x23-\x5b\x5d-\x7e]`) + startOfLineRx = regexp.MustCompile(`(?m)^`) +) func (c *Config) commentTemplateFunc(prefix, s string) string { return startOfLineRx.ReplaceAllString(s, prefix) @@ -308,7 +313,11 @@ func writeIniMap(w io.Writer, data map[string]any, sectionPrefix string) error { } subsections = append(subsections, subsection) case string: - fmt.Fprintf(w, "%s = %q\n", key, value) + if needsQuote(value) { + fmt.Fprintf(w, "%s = %q\n", key, value) + } else { + fmt.Fprintf(w, "%s = %s\n", key, value) + } default: return fmt.Errorf("%s%s: %T: unsupported type", sectionPrefix, key, value) } @@ -327,6 +336,22 @@ func writeIniMap(w io.Writer, data map[string]any, sectionPrefix string) error { return nil } +func needsQuote(s string) bool { + if s == "" { + return true + } + if needsQuoteRx.MatchString(s) { + return true + } + if _, err := strconv.ParseBool(s); err == nil { + return true + } + if _, err := strconv.ParseFloat(s, 64); err == nil { + return true + } + return false +} + func sortedKeys[K constraints.Ordered, V any](m map[K]V) []K { keys := maps.Keys(m) slices.Sort(keys) diff --git a/pkg/cmd/templatefuncs_test.go b/pkg/cmd/templatefuncs_test.go index cd9bf756e8b..40854fa74ba 100644 --- a/pkg/cmd/templatefuncs_test.go +++ b/pkg/cmd/templatefuncs_test.go @@ -63,7 +63,21 @@ func TestToIniTemplateFunc(t *testing.T) { `bool = true`, `float = 1.000000`, `int = 1`, - `string = "string"`, + `string = string`, + ), + }, + { + data: map[string]any{ + "bool": "true", + "float": "1.0", + "int": "1", + "string": "string string", + }, + expected: chezmoitest.JoinLines( + `bool = "true"`, + `float = "1.0"`, + `int = "1"`, + `string = "string string"`, ), }, { @@ -74,10 +88,10 @@ func TestToIniTemplateFunc(t *testing.T) { }, }, expected: chezmoitest.JoinLines( - `key = "value"`, + `key = value`, ``, `[section]`, - `subKey = "subValue"`, + `subKey = subValue`, ), }, { @@ -93,7 +107,7 @@ func TestToIniTemplateFunc(t *testing.T) { `[section]`, ``, `[section.subsection]`, - `subSubKey = "subSubValue"`, + `subSubKey = subSubValue`, ), }, { @@ -107,13 +121,13 @@ func TestToIniTemplateFunc(t *testing.T) { }, }, expected: chezmoitest.JoinLines( - `key = "value"`, + `key = value`, ``, `[section]`, - `subKey = "subValue"`, + `subKey = subValue`, ``, `[section.subsection]`, - `subSubKey = "subSubValue"`, + `subSubKey = subSubValue`, ), }, { @@ -140,22 +154,22 @@ func TestToIniTemplateFunc(t *testing.T) { expected: chezmoitest.JoinLines( ``, `[section1]`, - `subKey1 = "subValue1"`, + `subKey1 = subValue1`, ``, `[section1.subsection1a]`, - `subSubKey1a = "subSubValue1a"`, + `subSubKey1a = subSubValue1a`, ``, `[section1.subsection1b]`, - `subSubKey1b = "subSubValue1b"`, + `subSubKey1b = subSubValue1b`, ``, `[section2]`, - `subKey2 = "subValue2"`, + `subKey2 = subValue2`, ``, `[section2.subsection2a]`, - `subSubKey2a = "subSubValue2a"`, + `subSubKey2a = subSubValue2a`, ``, `[section2.subsection2b]`, - `subSubKey2b = "subSubValue2b"`, + `subSubKey2b = subSubValue2b`, ), }, } { @@ -167,6 +181,42 @@ func TestToIniTemplateFunc(t *testing.T) { } } +func TestNeedsQuote(t *testing.T) { + for i, tc := range []struct { + s string + expected bool + }{ + { + s: "", + expected: true, + }, + { + s: "\\", + expected: true, + }, + { + s: "\a", + expected: true, + }, + { + s: "abc", + expected: false, + }, + { + s: "true", + expected: true, + }, + { + s: "1", + expected: true, + }, + } { + t.Run(strconv.Itoa(i), func(t *testing.T) { + assert.Equal(t, tc.expected, needsQuote(tc.s)) + }) + } +} + func TestQuoteListTemplateFunc(t *testing.T) { c, err := newConfig() require.NoError(t, err) diff --git a/pkg/cmd/testdata/scripts/templatefuncs.txtar b/pkg/cmd/testdata/scripts/templatefuncs.txtar index 6af5ff99619..e84176036bc 100644 --- a/pkg/cmd/testdata/scripts/templatefuncs.txtar +++ b/pkg/cmd/testdata/scripts/templatefuncs.txtar @@ -154,10 +154,10 @@ file2.txt -- golden/include-relpath -- # contents of .local/share/chezmoi/.include -- golden/toIni -- -key = "value" +key = value [section] -subkey = "subvalue" +subkey = subvalue -- home/user/.include -- # contents of .include -- home/user/.local/share/chezmoi/.include --