From 54419202fe641775651c11d97f1a1397e84dbc0c Mon Sep 17 00:00:00 2001 From: Ben Moskovitz Date: Wed, 5 Jun 2024 11:48:10 +1000 Subject: [PATCH] Fix literal dollar sign escapes --- interpolate_test.go | 1 + parser.go | 26 +++++++++++++++++--------- parser_test.go | 8 ++++++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/interpolate_test.go b/interpolate_test.go index 9c996d3..d33deda 100644 --- a/interpolate_test.go +++ b/interpolate_test.go @@ -290,6 +290,7 @@ func TestEscapingVariables(t *testing.T) { {`Do this \${SUCH_ESCAPE}`, `Do this ${SUCH_ESCAPE}`}, {`Do this $${SUCH_ESCAPE:-$OTHERWISE}`, `Do this ${SUCH_ESCAPE:-$OTHERWISE}`}, {`Do this \${SUCH_ESCAPE:-$OTHERWISE}`, `Do this ${SUCH_ESCAPE:-$OTHERWISE}`}, + {`echo "my favourite mountain is cotopaxi" | grep 'xi$$'`, `echo "my favourite mountain is cotopaxi" | grep 'xi$'`}, } { result, err := interpolate.Interpolate(nil, tc.Str) if err != nil { diff --git a/parser.go b/parser.go index 52954c2..c69b8bf 100644 --- a/parser.go +++ b/parser.go @@ -123,20 +123,28 @@ func (p *Parser) parseExpression(stop ...rune) (Expression, error) { } func (p *Parser) parseEscapedExpansion() (Expansion, error) { - // if it's an escaped brace expansion, (eg $${MY_COOL_VAR:-5}) consume text until the close brace - if c := p.peekRune(); c == '{' { + next := p.peekRune() + switch { + case next == '{': + // if it's an escaped brace expansion, (eg $${MY_COOL_VAR:-5}) consume text until the close brace id := p.scanUntil(func(r rune) bool { return r == '}' }) id = id + string(p.nextRune()) // we know that the next rune is a close brace, chuck it on the end return EscapedExpansion{Identifier: id}, nil - } - // otherwise, it's an escaped identifier (eg $$MY_COOL_VAR) - id, err := p.scanIdentifier() - if err != nil { - return nil, err - } + case unicode.IsLetter(next): + // it's an escaped identifier (eg $$MY_COOL_VAR) + id, err := p.scanIdentifier() + if err != nil { + return nil, err + } - return EscapedExpansion{Identifier: id}, nil + return EscapedExpansion{Identifier: id}, nil + + default: + // there's no identifier or brace afterward, so it's probably a literal escaped dollar sign, so return an empty identifier + // that will be expanded to a single dollar sign + return EscapedExpansion{Identifier: ""}, nil + } } func (p *Parser) parseExpansion() (Expansion, error) { diff --git a/parser_test.go b/parser_test.go index 747ccdf..a223f93 100644 --- a/parser_test.go +++ b/parser_test.go @@ -171,6 +171,14 @@ func TestParser(t *testing.T) { String: "$$MOUNTAIN", Expected: []interpolate.ExpressionItem{{Expansion: interpolate.EscapedExpansion{Identifier: "MOUNTAIN"}}}, }, + { + String: "this is a regex! /^start.*end$$/", // the dollar sign at the end of the regex has to be escaped to be treated as a literal dollar sign by this library + Expected: []interpolate.ExpressionItem{ + {Text: "this is a regex! /^start.*end"}, + {Expansion: interpolate.EscapedExpansion{Identifier: ""}}, + {Text: "/"}, + }, + }, } for _, tc := range testCases {