diff --git a/envsubst_test.go b/envsubst_test.go index 8b8746a..c1e32cd 100644 --- a/envsubst_test.go +++ b/envsubst_test.go @@ -1,7 +1,6 @@ package envsubst import ( - "io/ioutil" "os" "testing" ) @@ -23,8 +22,9 @@ func TestIntegration(t *testing.T) { t.Error("Expect bytes integration test to pass") } bytes, err = ReadFile("testdata/file.tmpl") - fexpected, err := ioutil.ReadFile("testdata/file.out") + fexpected, err := os.ReadFile("testdata/file.out") if string(bytes) != string(fexpected) || err != nil { + t.Logf("output:\n%s\nexpected:\n%s\n", string(bytes), string(fexpected)) t.Error("Expect ReadFile integration test to pass") } } diff --git a/parse/lex.go b/parse/lex.go index 5cd4c46..3637616 100644 --- a/parse/lex.go +++ b/parse/lex.go @@ -59,15 +59,16 @@ type stateFn func(*lexer) stateFn // lexer holds the state of the scanner type lexer struct { - input string // the string being lexed - state stateFn // the next lexing function to enter - pos Pos // current position in the input - start Pos // start position of this item - width Pos // width of last rune read from input - lastPos Pos // position of most recent item returned by nextItem - items chan item // channel of lexed items - subsDepth int // depth of substitution - noDigit bool // if the lexer skips variables that start with a digit + input string // the string being lexed + state stateFn // the next lexing function to enter + pos Pos // current position in the input + start Pos // start position of this item + width Pos // width of last rune read from input + lastPos Pos // position of most recent item returned by nextItem + items chan item // channel of lexed items + subsDepth int // depth of substitution + braceDepth int // depth of braces within default value + noDigit bool // if the lexer skips variables that start with a digit } // next returns the next rune in the input. @@ -242,10 +243,18 @@ func lexSubstitutionOperator(l *lexer) stateFn { // lexSubstitution scans the elements inside substitution delimiters. func lexSubstitution(l *lexer) stateFn { switch r := l.next(); { + case r == '{': + l.braceDepth++ + l.emit(itemText) case r == '}': - l.subsDepth-- - l.emit(itemRightDelim) - return lexText + if l.braceDepth > 0 { + l.braceDepth-- + l.emit(itemText) + } else { + l.subsDepth-- + l.emit(itemRightDelim) + return lexText + } case r == eof || isEndOfLine(r): return l.errorf("closing brace expected") case isAlphaNumeric(r) && strings.HasPrefix(l.input[l.lastPos:], "${"): diff --git a/parse/lex_test.go b/parse/lex_test.go index 34f4583..804c4c0 100644 --- a/parse/lex_test.go +++ b/parse/lex_test.go @@ -145,6 +145,87 @@ var lexTests = []lexTest{ {itemText, 10, "ABC}"}, tEOF, }}, + {"braces in default", "${BAR:-{}}", []item{ + tLeft, + {itemVariable, 0, "BAR"}, + tColDash, + {itemText, 0, "{"}, + {itemText, 0, "}"}, + tRight, + tEOF, + }}, + {"nested braces in default", "${ENV:-{\"key\":\"value\"}}", []item{ + tLeft, + {itemVariable, 0, "ENV"}, + tColDash, + {itemText, 0, "{"}, + {itemText, 0, "\""}, + {itemText, 0, "k"}, + {itemText, 0, "e"}, + {itemText, 0, "y"}, + {itemText, 0, "\""}, + {itemText, 0, ":"}, + {itemText, 0, "\""}, + {itemText, 0, "v"}, + {itemText, 0, "a"}, + {itemText, 0, "l"}, + {itemText, 0, "u"}, + {itemText, 0, "e"}, + {itemText, 0, "\""}, + {itemText, 0, "}"}, + tRight, + tEOF, + }}, + {"multi-level nested braces in default", "${ENV:-{\"key\":{\"subkey\":\"subval\"},\"key2\":\"value2\"}}", []item{ + tLeft, + {itemVariable, 0, "ENV"}, + tColDash, + {itemText, 0, "{"}, + {itemText, 0, "\""}, + {itemText, 0, "k"}, + {itemText, 0, "e"}, + {itemText, 0, "y"}, + {itemText, 0, "\""}, + {itemText, 0, ":"}, + {itemText, 0, "{"}, + {itemText, 0, "\""}, + {itemText, 0, "s"}, + {itemText, 0, "u"}, + {itemText, 0, "b"}, + {itemText, 0, "k"}, + {itemText, 0, "e"}, + {itemText, 0, "y"}, + {itemText, 0, "\""}, + {itemText, 0, ":"}, + {itemText, 0, "\""}, + {itemText, 0, "s"}, + {itemText, 0, "u"}, + {itemText, 0, "b"}, + {itemText, 0, "v"}, + {itemText, 0, "a"}, + {itemText, 0, "l"}, + {itemText, 0, "\""}, + {itemText, 0, "}"}, + {itemText, 0, ","}, + {itemText, 0, "\""}, + {itemText, 0, "k"}, + {itemText, 0, "e"}, + {itemText, 0, "y"}, + {itemText, 0, "2"}, + {itemText, 0, "\""}, + {itemText, 0, ":"}, + {itemText, 0, "\""}, + {itemText, 0, "v"}, + {itemText, 0, "a"}, + {itemText, 0, "l"}, + {itemText, 0, "u"}, + {itemText, 0, "e"}, + {itemText, 0, "2"}, + {itemText, 0, "\""}, + {itemText, 0, "}"}, + tRight, + tEOF, + }}, } func TestLex(t *testing.T) { diff --git a/testdata/file.out b/testdata/file.out index 2cdde7b..3656142 100644 --- a/testdata/file.out +++ b/testdata/file.out @@ -1,5 +1,7 @@ foo: bar baz: baz env: dev +curly_bar: bar +curly: {"key":{"subkey":"subval"},"key2":"value2"} uri: http://bar.com/foo host: localhost diff --git a/testdata/file.tmpl b/testdata/file.tmpl index 59f5077..313f888 100644 --- a/testdata/file.tmpl +++ b/testdata/file.tmpl @@ -1,5 +1,7 @@ foo: $BAR baz: ${FOO:=baz} env: ${ENV:-dev} +curly_bar: ${BAR:-{}} +curly: ${ENV:-{"key":{"subkey":"subval"},"key2":"value2"}} uri: http://${BAR:=$BAZ}.com/foo host: ${BAR:+localhost}