diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py index 3af0126ef..7f101659a 100644 --- a/beets/util/functemplate.py +++ b/beets/util/functemplate.py @@ -32,6 +32,7 @@ FUNC_DELIM = u'%' GROUP_OPEN = u'{' GROUP_CLOSE = u'}' ARG_SEP = u',' +ESCAPE_CHAR = u'$' class Environment(object): """Contains the values and functions to be substituted into a @@ -139,7 +140,7 @@ class Parser(object): char = self.string[self.pos] if char not in (SYMBOL_DELIM, FUNC_DELIM, GROUP_OPEN, - GROUP_CLOSE, ARG_SEP): + GROUP_CLOSE, ARG_SEP, ESCAPE_CHAR): # A non-special character. # TODO: This can be made more efficient by repeatedly asking # for the next special character rather than walking through @@ -158,9 +159,13 @@ class Parser(object): break next_char = self.string[self.pos + 1] - if char == next_char: - # An escaped special character ($$, etc.). - text_parts.append(char) + if char == ESCAPE_CHAR and next_char in \ + (SYMBOL_DELIM, FUNC_DELIM, GROUP_CLOSE, ARG_SEP): + # An escaped special character ($$, $}, etc.). Note that + # ${ is not an escape sequence: this is ambiguous with + # the start of a symbol and it's not necessary (just + # using { suffices in all cases). + text_parts.append(next_char) self.pos += 2 # Skip the next character. continue diff --git a/test/test_template.py b/test/test_template.py index 779734616..29d5d384f 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -86,16 +86,13 @@ class ParseTest(unittest.TestCase): self.assertEqual(list(_normparse(u'hello $$')), [u'hello $']) def test_escaped_function_delim(self): - self.assertEqual(list(_normparse(u'a %% b')), [u'a % b']) + self.assertEqual(list(_normparse(u'a $% b')), [u'a % b']) def test_escaped_sep(self): - self.assertEqual(list(_normparse(u'a ,, b')), [u'a , b']) - - def test_escaped_open_brace(self): - self.assertEqual(list(_normparse(u'a {{ b')), [u'a { b']) + self.assertEqual(list(_normparse(u'a $, b')), [u'a , b']) def test_escaped_close_brace(self): - self.assertEqual(list(_normparse(u'a }} b')), [u'a } b']) + self.assertEqual(list(_normparse(u'a $} b')), [u'a } b']) def test_bare_value_delim_kept_intact(self): self.assertEqual(list(_normparse(u'a $ b')), [u'a $ b']) @@ -169,13 +166,13 @@ class ParseTest(unittest.TestCase): self.assertEqual(list(_normexpr(parts[0].args[1])), [u'baz']) def test_call_with_escaped_sep(self): - parts = list(_normparse(u'%foo{bar,,baz}')) + parts = list(_normparse(u'%foo{bar$,baz}')) self.assertEqual(len(parts), 1) self._assert_call(parts[0], u"foo", 1) self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar,baz']) def test_call_with_escaped_close(self): - parts = list(_normparse(u'%foo{bar}}baz}')) + parts = list(_normparse(u'%foo{bar$}baz}')) self.assertEqual(len(parts), 1) self._assert_call(parts[0], u"foo", 1) self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar}baz']) @@ -204,7 +201,8 @@ class ParseTest(unittest.TestCase): self._assert_call(parts[0], u"foo", 1) arg_parts = list(_normexpr(parts[0].args[0])) self.assertEqual(len(arg_parts), 1) - self._assert_call(arg_parts[0], u"bar", 0) + self._assert_call(arg_parts[0], u"bar", 1) + self.assertEqual(list(_normexpr(arg_parts[0].args[0])), [u'baz']) class EvalTest(unittest.TestCase): def _eval(self, template):