diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py index 5547bcb0e5..228af9352f 100644 --- a/beets/util/functemplate.py +++ b/beets/util/functemplate.py @@ -380,8 +380,13 @@ def parse_symbol(self): # A symbol like ${this}. self.pos += 1 # Skip opening. closer = self.string.find(GROUP_CLOSE, self.pos) - if closer == -1 or closer == self.pos: - # No closing brace found or identifier is empty. + if closer == -1: + # No closing brace found. + self.parts.append(self.string[start_pos : self.pos]) + elif closer == self.pos: + # Empty identifier: consume through the closing brace so it + # cannot be mistaken for an enclosing call's terminator. + self.pos = closer + 1 self.parts.append(self.string[start_pos : self.pos]) else: # Closer found. diff --git a/docs/changelog.rst b/docs/changelog.rst index 096a35a6db..b94d923c55 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -95,6 +95,9 @@ Bug fixes valid date/time string" error instead of crashing with an uncaught ``KeyError``. A ``|`` was being accepted as a relative-date unit due to a regular expression character-class typo. +- Path formats: an empty ``${}`` inside a function call argument no longer + truncates the argument list and leaks the rest of the template as literal + text. .. For plugin developers diff --git a/test/util/test_functemplate.py b/test/util/test_functemplate.py index 243c03740c..a377feaa94 100644 --- a/test/util/test_functemplate.py +++ b/test/util/test_functemplate.py @@ -115,6 +115,19 @@ def test_unclosed_braces_symbol(self): def test_empty_braces_symbol(self): assert list(_normparse("a ${} b")) == ["a ${} b"] + def test_empty_braces_symbol_inside_call_arg(self): + parts = list(_normparse("%foo{a${}b}")) + assert len(parts) == 1 + self._assert_call(parts[0], "foo", 1) + assert list(_normexpr(parts[0].args[0])) == ["a${}b"] + + def test_empty_braces_symbol_inside_call_arg_list(self): + parts = list(_normparse("%foo{a${}b,baz}")) + assert len(parts) == 1 + self._assert_call(parts[0], "foo", 2) + assert list(_normexpr(parts[0].args[0])) == ["a${}b"] + assert list(_normexpr(parts[0].args[1])) == ["baz"] + def test_call_without_args_at_end(self): assert list(_normparse("foo %bar")) == ["foo %bar"]