Skip to content

Commit dd0d02e

Browse files
committed
parse quotes from file
1 parent 58d318b commit dd0d02e

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

pkg/kvfile/kvfile.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
//
1616
// # Interpolation, substitution, and escaping
1717
//
18-
// Both keys and values are used as-is; no interpolation, substitution or
19-
// escaping is supported, and quotes are considered part of the key or value.
18+
// Both keys and values are used as-is; no interpolation or substitution is supported.
19+
// Quotes around values are removed if the value starts and ends with the same quote character.
2020
// Whitespace in values (including leading and trailing) is preserved. Given
2121
// that the file format is line-delimited, neither key, nor value, can contain
2222
// newlines.
@@ -78,6 +78,20 @@ func ParseFromReader(r io.Reader, lookupFn func(key string) (value string, found
7878

7979
const whiteSpaces = " \t"
8080

81+
// trimQuotes removes the quotes from a string if the string starts and ends with the same quote character.
82+
func trimQuotes(value string) string {
83+
if len(value) < 2 {
84+
return value
85+
}
86+
lastIndex := len(value) - 1
87+
for _, char := range []byte{'\'', '"'} {
88+
if value[0] == char && value[lastIndex] == char {
89+
return value[1:lastIndex]
90+
}
91+
}
92+
return value
93+
}
94+
8195
func parseKeyValueFile(r io.Reader, lookupFn func(string) (string, bool)) ([]string, error) {
8296
lines := []string{}
8397
scanner := bufio.NewScanner(r)
@@ -113,15 +127,22 @@ func parseKeyValueFile(r io.Reader, lookupFn func(string) (string, bool)) ([]str
113127
}
114128

115129
if hasValue {
116-
// key/value pair is valid and has a value; add the line as-is.
117-
lines = append(lines, line)
130+
// key/value pair is valid and has a value
131+
// Split the line to get the key and value
132+
key, value, _ := strings.Cut(line, "=")
133+
// Trim quotes from the value
134+
value = trimQuotes(value)
135+
// Reconstruct the line with the trimmed value
136+
lines = append(lines, key+"="+value)
118137
continue
119138
}
120139

121140
if lookupFn != nil {
122141
// No value given; try to look up the value. The value may be
123142
// empty but if no value is found, the key is omitted.
124143
if value, found := lookupFn(line); found {
144+
// Trim quotes from the value
145+
value = trimQuotes(value)
125146
lines = append(lines, key+"="+value)
126147
}
127148
}

pkg/kvfile/kvfile_test.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ VAR=VAR_VALUE
2424
EMPTY_VAR=
2525
UNDEFINED_VAR
2626
DEFINED_VAR
27+
QUOTED_VAR
28+
QUOTED_VALUE="this should be quoted"
2729
`
2830
vars := map[string]string{
2931
"DEFINED_VAR": "defined-value",
32+
"QUOTED_VAR": "\"quoted value\"",
3033
}
3134
lookupFn := func(name string) (string, bool) {
3235
v, ok := vars[name]
@@ -40,7 +43,13 @@ DEFINED_VAR
4043
variables, err := Parse(fileName, lookupFn)
4144
assert.NilError(t, err)
4245

43-
expectedLines := []string{"VAR=VAR_VALUE", "EMPTY_VAR=", "DEFINED_VAR=defined-value"}
46+
expectedLines := []string{
47+
"VAR=VAR_VALUE",
48+
"EMPTY_VAR=",
49+
"DEFINED_VAR=defined-value",
50+
"QUOTED_VAR=quoted value",
51+
"QUOTED_VALUE=this should be quoted",
52+
}
4453
assert.Check(t, is.DeepEqual(variables, expectedLines))
4554
}
4655

@@ -118,9 +127,11 @@ VAR=VAR_VALUE
118127
EMPTY_VAR=
119128
UNDEFINED_VAR
120129
DEFINED_VAR
130+
QUOTED_VAR
121131
`
122132
vars := map[string]string{
123133
"DEFINED_VAR": "defined-value",
134+
"QUOTED_VAR": "\"quoted value\"",
124135
}
125136
lookupFn := func(name string) (string, bool) {
126137
v, ok := vars[name]
@@ -130,7 +141,12 @@ DEFINED_VAR
130141
variables, err := ParseFromReader(strings.NewReader(content), lookupFn)
131142
assert.NilError(t, err)
132143

133-
expectedLines := []string{"VAR=VAR_VALUE", "EMPTY_VAR=", "DEFINED_VAR=defined-value"}
144+
expectedLines := []string{
145+
"VAR=VAR_VALUE",
146+
"EMPTY_VAR=",
147+
"DEFINED_VAR=defined-value",
148+
"QUOTED_VAR=quoted value",
149+
}
134150
assert.Check(t, is.DeepEqual(variables, expectedLines))
135151
}
136152

@@ -143,3 +159,29 @@ func TestParseFromReaderWithNoName(t *testing.T) {
143159
const expectedMessage = "no variable name on line '=blank variable names are an error case'"
144160
assert.Check(t, is.ErrorContains(err, expectedMessage))
145161
}
162+
163+
// Test ParseFromReader with quoted values
164+
func TestParseFromReaderWithQuotes(t *testing.T) {
165+
content := `# Test with quotes
166+
DOUBLE_QUOTES="double quoted value"
167+
SINGLE_QUOTES='single quoted value'
168+
MIXED_QUOTES="'mixed' quotes"
169+
NESTED_QUOTES='"nested" quotes'
170+
UNBALANCED_QUOTES="unbalanced quotes
171+
UNBALANCED_QUOTES2=unbalanced quotes"
172+
QUOTES_IN_MIDDLE=value "with" quotes
173+
`
174+
variables, err := ParseFromReader(strings.NewReader(content), nil)
175+
assert.NilError(t, err)
176+
177+
expectedLines := []string{
178+
"DOUBLE_QUOTES=double quoted value",
179+
"SINGLE_QUOTES=single quoted value",
180+
"MIXED_QUOTES='mixed' quotes",
181+
"NESTED_QUOTES=\"nested\" quotes",
182+
"UNBALANCED_QUOTES=\"unbalanced quotes",
183+
"UNBALANCED_QUOTES2=unbalanced quotes\"",
184+
"QUOTES_IN_MIDDLE=value \"with\" quotes",
185+
}
186+
assert.Check(t, is.DeepEqual(variables, expectedLines))
187+
}

0 commit comments

Comments
 (0)