diff --git a/README.md b/README.md index 8aa2744..a8f5c2c 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,16 @@ func main() { |__Expression__ | __Meaning__ | | ----------------- | -------------- | -|`${var}` | Value of var (same as `$var`) +|`${var}` | Value of var (same as `$var`) |`${var-$DEFAULT}` | If var not set, evaluate expression as $DEFAULT |`${var:-$DEFAULT}` | If var not set or is empty, evaluate expression as $DEFAULT |`${var=$DEFAULT}` | If var not set, evaluate expression as $DEFAULT |`${var:=$DEFAULT}` | If var not set or is empty, evaluate expression as $DEFAULT -|`${var+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string +|`${var+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string |`${var:+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string -table taken from [here](http://www.tldp.org/LDP/abs/html/refcards.html#AEN22728) +|`$$var` | Escape expressions. Result will be `$var`. + +Most of the rows in this table were taken from [here](http://www.tldp.org/LDP/abs/html/refcards.html#AEN22728) ### See also diff --git a/parse/lex.go b/parse/lex.go index 49236b7..f7fd4df 100644 --- a/parse/lex.go +++ b/parse/lex.go @@ -144,17 +144,24 @@ Loop: switch r := l.next(); r { case '$': l.pos-- + // emit the text we've found until here, if any. if l.pos > l.start { l.emit(itemText) } l.pos++ - if r := l.next(); isAlphaNumeric(r) { - l.backup() - return lexVariable - } else if r == '{' { + switch r := l.peek(); { + case r == '$': + // ignore the previous '$'. + l.ignore() + l.next() + l.emit(itemText) + case r == '{': + l.next() l.subsDepth++ l.emit(itemLeftDelim) return lexSubstitution + case isAlphaNumeric(r): + return lexVariable } case eof: break Loop diff --git a/parse/lex_test.go b/parse/lex_test.go index 080fa82..b8f8ed5 100644 --- a/parse/lex_test.go +++ b/parse/lex_test.go @@ -81,13 +81,25 @@ var lexTests = []lexTest{ {itemVariable, 0, "world"}, {itemError, 0, "closing brace expected"}, }}, + {"escaping $$var", "hello $$HOME", []item{ + {itemText, 0, "hello "}, + {itemText, 7, "$"}, + {itemText, 8, "HOME"}, + tEOF, + }}, + {"escaping $${subst}", "hello $${HOME}", []item{ + {itemText, 0, "hello "}, + {itemText, 7, "$"}, + {itemText, 8, "{HOME}"}, + tEOF, + }}, } func TestLex(t *testing.T) { for _, test := range lexTests { items := collect(&test) if !equal(items, test.items, false) { - t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items) + t.Errorf("%s:\ninput\n\t%q\ngot\n\t%+v\nexpected\n\t%v", test.name, test.input, items, test.items) } } } diff --git a/parse/parse_test.go b/parse/parse_test.go index 7152317..af6f498 100644 --- a/parse/parse_test.go +++ b/parse/parse_test.go @@ -99,6 +99,12 @@ var parseTests = []parseTest{ {"$var and $DEFAULT empty :=", "${EMPTY:=$ALSO_EMPTY}", "", errEmpty}, {"$var and $OTHER empty +", "${EMPTY+$ALSO_EMPTY}", "", errEmpty}, {"$var and $OTHER empty :+", "${EMPTY:+$ALSO_EMPTY}", "", errEmpty}, + + // escaping. + {"escape $$var", "FOO $$BAR BAZ", "FOO $BAR BAZ", errNone}, + {"escape $${subst}", "FOO $${BAR} BAZ", "FOO ${BAR} BAZ", errNone}, + {"escape $$$var", "$$$BAR", "$bar", errNone}, + {"escape $$${subst}", "$$${BAZ:-baz}", "$baz", errNone}, } func TestParse(t *testing.T) {