Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions beets/util/functemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions test/util/test_functemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
Loading