diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index 0a050794c4..10fba65322 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -17,6 +17,7 @@ from __future__ import annotations import os +from shlex import quote as shell_quote from typing import TYPE_CHECKING, Any, TypeAlias from urllib.parse import quote from urllib.request import pathname2url @@ -160,8 +161,10 @@ def update_cmd(self, lib: Library, opts: Any, args: list[str]) -> None: } if not playlists: unmatched = [name for name, _, _ in self._unmatched_playlists] + unmatched.sort() + quoted_names = " ".join(shell_quote(name) for name in unmatched) raise ui.UserError( - f"No playlist matching any of {unmatched} found" + f"No playlist matching any of {quoted_names} found" ) self._matched_playlists = playlists @@ -348,7 +351,7 @@ def update_playlists(self, lib: Library, pretend: bool = False) -> None: if not pretend: # Write all of the accumulated track lists to files. - for m3u in m3us: + for m3u, entries in m3us.items(): m3u_path = normpath( os.path.join(playlist_dir, bytestring_path(m3u)) ) @@ -364,7 +367,7 @@ def update_playlists(self, lib: Library, pretend: bool = False) -> None: if extm3u: keys = self.config["fields"].get(list) f.write(b"#EXTM3U\n") - for entry in m3us[m3u]: + for entry in entries: item = entry.item comment = "" if extm3u: diff --git a/docs/changelog.rst b/docs/changelog.rst index 80cbf0e9d3..dd999ab689 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,10 @@ New features deprecate ``overwrite``. - :doc:`plugins/autobpm`: The "BPM already exists for item" log message can now be hidden with the ``--quiet`` flag. +- :doc:`plugins/smartplaylist`: The list of available playlists shown when an + unknown playlist name is passed as an argument is now sorted alphabetically + and printed space-delimited and POSIX shell-quoted when required. This makes + it easier to copy and paste multiple playlists for further use in the shell. Bug fixes ~~~~~~~~~ diff --git a/test/plugins/test_smartplaylist.py b/test/plugins/test_smartplaylist.py index c62021184c..f2ecd3dda0 100644 --- a/test/plugins/test_smartplaylist.py +++ b/test/plugins/test_smartplaylist.py @@ -574,3 +574,20 @@ def test_splupdate(self): for name in (b"my_playlist.m3u", b"all.m3u"): with open(path.join(self.temp_dir, name), "rb") as f: assert f.read() == self.item.path + b"\n" + + def test_splupdate_unknown_playlist_error_is_sorted_and_quoted(self): + config["smartplaylist"]["playlists"].set( + [ + {"name": "z last.m3u", "query": self.item.title}, + {"name": "rock'n roll.m3u", "query": self.item.title}, + {"name": "a one.m3u", "query": self.item.title}, + ] + ) + + with pytest.raises(UserError) as exc_info: + self.run_with_output("splupdate", "tagada") + + assert str(exc_info.value) == ( + "No playlist matching any of " + "'a one.m3u' 'rock'\"'\"'n roll.m3u' 'z last.m3u' found" + )