Skip to content

Commit 7ca31b7

Browse files
committed
Fix for bug when curly braces are in the default value.
1 parent 795eb83 commit 7ca31b7

5 files changed

Lines changed: 108 additions & 14 deletions

File tree

envsubst_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package envsubst
22

33
import (
4-
"io/ioutil"
54
"os"
65
"testing"
76
)
@@ -23,8 +22,9 @@ func TestIntegration(t *testing.T) {
2322
t.Error("Expect bytes integration test to pass")
2423
}
2524
bytes, err = ReadFile("testdata/file.tmpl")
26-
fexpected, err := ioutil.ReadFile("testdata/file.out")
25+
fexpected, err := os.ReadFile("testdata/file.out")
2726
if string(bytes) != string(fexpected) || err != nil {
27+
t.Logf("output:\n%s\nexpected:\n%s\n", string(bytes), string(fexpected))
2828
t.Error("Expect ReadFile integration test to pass")
2929
}
3030
}

parse/lex.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,16 @@ type stateFn func(*lexer) stateFn
5959

6060
// lexer holds the state of the scanner
6161
type lexer struct {
62-
input string // the string being lexed
63-
state stateFn // the next lexing function to enter
64-
pos Pos // current position in the input
65-
start Pos // start position of this item
66-
width Pos // width of last rune read from input
67-
lastPos Pos // position of most recent item returned by nextItem
68-
items chan item // channel of lexed items
69-
subsDepth int // depth of substitution
70-
noDigit bool // if the lexer skips variables that start with a digit
62+
input string // the string being lexed
63+
state stateFn // the next lexing function to enter
64+
pos Pos // current position in the input
65+
start Pos // start position of this item
66+
width Pos // width of last rune read from input
67+
lastPos Pos // position of most recent item returned by nextItem
68+
items chan item // channel of lexed items
69+
subsDepth int // depth of substitution
70+
braceDepth int // depth of braces within default value
71+
noDigit bool // if the lexer skips variables that start with a digit
7172
}
7273

7374
// next returns the next rune in the input.
@@ -242,10 +243,18 @@ func lexSubstitutionOperator(l *lexer) stateFn {
242243
// lexSubstitution scans the elements inside substitution delimiters.
243244
func lexSubstitution(l *lexer) stateFn {
244245
switch r := l.next(); {
246+
case r == '{':
247+
l.braceDepth++
248+
l.emit(itemText)
245249
case r == '}':
246-
l.subsDepth--
247-
l.emit(itemRightDelim)
248-
return lexText
250+
if l.braceDepth > 0 {
251+
l.braceDepth--
252+
l.emit(itemText)
253+
} else {
254+
l.subsDepth--
255+
l.emit(itemRightDelim)
256+
return lexText
257+
}
249258
case r == eof || isEndOfLine(r):
250259
return l.errorf("closing brace expected")
251260
case isAlphaNumeric(r) && strings.HasPrefix(l.input[l.lastPos:], "${"):

parse/lex_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,87 @@ var lexTests = []lexTest{
145145
{itemText, 10, "ABC}"},
146146
tEOF,
147147
}},
148+
{"braces in default", "${BAR:-{}}", []item{
149+
tLeft,
150+
{itemVariable, 0, "BAR"},
151+
tColDash,
152+
{itemText, 0, "{"},
153+
{itemText, 0, "}"},
154+
tRight,
155+
tEOF,
156+
}},
157+
{"nested braces in default", "${ENV:-{\"key\":\"value\"}}", []item{
158+
tLeft,
159+
{itemVariable, 0, "ENV"},
160+
tColDash,
161+
{itemText, 0, "{"},
162+
{itemText, 0, "\""},
163+
{itemText, 0, "k"},
164+
{itemText, 0, "e"},
165+
{itemText, 0, "y"},
166+
{itemText, 0, "\""},
167+
{itemText, 0, ":"},
168+
{itemText, 0, "\""},
169+
{itemText, 0, "v"},
170+
{itemText, 0, "a"},
171+
{itemText, 0, "l"},
172+
{itemText, 0, "u"},
173+
{itemText, 0, "e"},
174+
{itemText, 0, "\""},
175+
{itemText, 0, "}"},
176+
tRight,
177+
tEOF,
178+
}},
179+
{"multi-level nested braces in default", "${ENV:-{\"key\":{\"subkey\":\"subval\"},\"key2\":\"value2\"}}", []item{
180+
tLeft,
181+
{itemVariable, 0, "ENV"},
182+
tColDash,
183+
{itemText, 0, "{"},
184+
{itemText, 0, "\""},
185+
{itemText, 0, "k"},
186+
{itemText, 0, "e"},
187+
{itemText, 0, "y"},
188+
{itemText, 0, "\""},
189+
{itemText, 0, ":"},
190+
{itemText, 0, "{"},
191+
{itemText, 0, "\""},
192+
{itemText, 0, "s"},
193+
{itemText, 0, "u"},
194+
{itemText, 0, "b"},
195+
{itemText, 0, "k"},
196+
{itemText, 0, "e"},
197+
{itemText, 0, "y"},
198+
{itemText, 0, "\""},
199+
{itemText, 0, ":"},
200+
{itemText, 0, "\""},
201+
{itemText, 0, "s"},
202+
{itemText, 0, "u"},
203+
{itemText, 0, "b"},
204+
{itemText, 0, "v"},
205+
{itemText, 0, "a"},
206+
{itemText, 0, "l"},
207+
{itemText, 0, "\""},
208+
{itemText, 0, "}"},
209+
{itemText, 0, ","},
210+
{itemText, 0, "\""},
211+
{itemText, 0, "k"},
212+
{itemText, 0, "e"},
213+
{itemText, 0, "y"},
214+
{itemText, 0, "2"},
215+
{itemText, 0, "\""},
216+
{itemText, 0, ":"},
217+
{itemText, 0, "\""},
218+
{itemText, 0, "v"},
219+
{itemText, 0, "a"},
220+
{itemText, 0, "l"},
221+
{itemText, 0, "u"},
222+
{itemText, 0, "e"},
223+
{itemText, 0, "2"},
224+
{itemText, 0, "\""},
225+
{itemText, 0, "}"},
226+
tRight,
227+
tEOF,
228+
}},
148229
}
149230

150231
func TestLex(t *testing.T) {

testdata/file.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
foo: bar
22
baz: baz
33
env: dev
4+
curly_bar: bar
5+
curly: {"key":{"subkey":"subval"},"key2":"value2"}
46
uri: http://bar.com/foo
57
host: localhost

testdata/file.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
foo: $BAR
22
baz: ${FOO:=baz}
33
env: ${ENV:-dev}
4+
curly_bar: ${BAR:-{}}
5+
curly: ${ENV:-{"key":{"subkey":"subval"},"key2":"value2"}}
46
uri: http://${BAR:=$BAZ}.com/foo
57
host: ${BAR:+localhost}

0 commit comments

Comments
 (0)