From 73d200184b9be301d280b32b039a85d534183088 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Sun, 25 Jan 2015 19:09:42 +0100 Subject: [PATCH 1/7] Implement __format__ on Album and Item Cut the need to format manually (and often incorrectly) when logging by implementing the __format__ magic method (see PEP 3101) on LibModel (the parent class of Album & Item). Based on a discussion in PR #1262 --- beets/library.py | 19 +++++++++++++++++++ test/test_library.py | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/beets/library.py b/beets/library.py index 9b3e4a238..a391bfb51 100644 --- a/beets/library.py +++ b/beets/library.py @@ -247,6 +247,21 @@ class LibModel(dbcore.Model): super(LibModel, self).add(lib) plugins.send('database_change', lib=self._db) + def __format__(self, spec): + if not spec: + spec = beets.config[self._format_config_key].get(unicode) + result = self.evaluate_template(spec) + if isinstance(spec, bytes): + return result.encode('utf8') + else: + return result + + def __str__(self): + return format(self).encode('utf8') + + def __unicode__(self): + return format(self) + class FormattedItemMapping(dbcore.db.FormattedMapping): """Add lookup for album-level fields. @@ -383,6 +398,8 @@ class Item(LibModel): _sorts = {'artist': SmartArtistSort} + _format_config_key = 'list_format_item' + @classmethod def _getters(cls): getters = plugins.item_field_getters() @@ -791,6 +808,8 @@ class Album(LibModel): """List of keys that are set on an album's items. """ + _format_config_key = 'list_format_album' + @classmethod def _getters(cls): # In addition to plugin-provided computed fields, also expose diff --git a/test/test_library.py b/test/test_library.py index 717cc1aa1..42e510a4e 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2015, Adrian Sampson. # @@ -1087,6 +1088,23 @@ class TemplateTest(_common.LibTestCase): self.album.store() self.assertEqual(self.i.evaluate_template('$foo'), 'baz') + def test_album_and_item_format(self): + config['list_format_album'] = u'foö $foo' + album = beets.library.Album() + album.foo = 'bar' + album.tagada = 'togodo' + self.assertEqual(u"{0}".format(album), u"foö bar") + self.assertEqual(u"{0:$tagada}".format(album), u"togodo") + self.assertEqual(unicode(album), u"foö bar") + self.assertEqual(str(album), b"fo\xc3\xb6 bar") + + config['list_format_item'] = 'bar $foo' + item = beets.library.Item() + item.foo = 'bar' + item.tagada = 'togodo' + self.assertEqual("{0}".format(item), "bar bar") + self.assertEqual("{0:$tagada}".format(item), "togodo") + class UnicodePathTest(_common.LibTestCase): def test_unicode_path(self): From 62cd6e37aa902ab755e54eb087a19ac2d1bd8da5 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Sun, 25 Jan 2015 20:57:50 +0100 Subject: [PATCH 2/7] Update ui.print_obj_(), add ui.format_() Code now relies on `format()` for items and albums displaying/logging. `ui.print_()` calls `unicode()` or `str()` on the strings so for most usages calling `ui.print_(obj)` replaces `ui.print_(obj, lib, None)`. Where there is a special format `ui.print_(format(obj, fmt))` is fine, but when `fmt` can be None then one has to call `ui.print_(ui.format_(obj, fmt))` -- which is what `ui.print_obj` now does. --- beets/ui/__init__.py | 40 ++++++++++++++++++++-------------------- beets/ui/commands.py | 10 ++++------ beetsplug/duplicates.py | 2 +- beetsplug/echonest.py | 2 +- beetsplug/mbsync.py | 9 ++------- beetsplug/missing.py | 4 ++-- beetsplug/random.py | 2 +- 7 files changed, 31 insertions(+), 38 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index c541ba2de..0385584ac 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -99,6 +99,12 @@ def print_(*strings): replaces it. """ if strings: + if not isinstance(strings[0], basestring): + try: + strings = map(unicode, strings) + except UnicodeError: + strings = map(bytes, strings) + if isinstance(strings[0], unicode): txt = u' '.join(strings) else: @@ -471,29 +477,23 @@ def get_replacements(): return replacements -def _pick_format(album, fmt=None): - """Pick a format string for printing Album or Item objects, - falling back to config options and defaults. - """ +def format_(obj, fmt): + """Print a object, intended for Album and Item + + This is equivalent to `format`, but the spec can be None + `fmt` is mandatory for otherwise one can just call `format(my_object)`""" if fmt: - return fmt - if album: - return config['list_format_album'].get(unicode) + return format(obj, fmt) else: - return config['list_format_item'].get(unicode) + return format(obj) -def print_obj(obj, lib, fmt=None): - """Print an Album or Item object. If `fmt` is specified, use that - format string. Otherwise, use the configured template. - """ - album = isinstance(obj, library.Album) - fmt = _pick_format(album, fmt) - if isinstance(fmt, Template): - template = fmt - else: - template = Template(fmt) - print_(obj.evaluate_template(template)) +def print_obj(obj, fmt): + """Print a object, intended for Album and Item + + This is equivalent to `print_ o format`, but the spec can be None + `fmt` is mandatory for otherwise one can just call `print_(my_object)`""" + return print_(format_(obj, fmt)) def term_width(): @@ -587,7 +587,7 @@ def show_model_changes(new, old=None, fields=None, always=False): # Print changes. if changes or always: - print_obj(old, old._db) + print_(old) if changes: print_(u'\n'.join(changes)) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 839263fc5..8fccc47a8 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -32,7 +32,6 @@ from beets import plugins from beets import importer from beets import util from beets.util import syspath, normpath, ancestry, displayable_path -from beets.util.functemplate import Template from beets import library from beets import config from beets import logging @@ -951,13 +950,12 @@ def list_items(lib, query, album, fmt): """Print out items in lib matching query. If album, then search for albums instead of single items. """ - tmpl = Template(ui._pick_format(album, fmt)) if album: for album in lib.albums(query): - ui.print_obj(album, lib, tmpl) + ui.print_obj(album, fmt) else: for item in lib.items(query): - ui.print_obj(item, lib, tmpl) + ui.print_obj(item, fmt) def list_func(lib, opts, args): @@ -999,7 +997,7 @@ def update_items(lib, query, album, move, pretend): for item in items: # Item deleted? if not os.path.exists(syspath(item.path)): - ui.print_obj(item, lib) + ui.print_(item) ui.print_(ui.colorize('red', u' deleted')) if not pretend: item.remove(True) @@ -1122,7 +1120,7 @@ def remove_items(lib, query, album, delete): # Show all the items. for item in items: - ui.print_obj(item, lib, fmt) + ui.print_obj(item, fmt) # Confirm with user. if not ui.input_yn(prompt, True): diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index 60a49d091..ea235295c 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -42,7 +42,7 @@ def _process_item(item, lib, copy=False, move=False, delete=False, raise UserError('%s: can\'t parse k=v tag: %s' % (PLUGIN, tag)) setattr(k, v) item.store() - print_obj(item, lib, fmt=format) + print_obj(item, format) def _checksum(item, prog, log): diff --git a/beetsplug/echonest.py b/beetsplug/echonest.py index a2b24bf20..817b96739 100644 --- a/beetsplug/echonest.py +++ b/beetsplug/echonest.py @@ -115,7 +115,7 @@ def similar(lib, src_item, threshold=0.15, fmt='${difference}: ${path}'): d = diff(item, src_item) if d < threshold: s = fmt.replace('${difference}', '{:2.2f}'.format(d)) - ui.print_obj(item, lib, s) + ui.print_obj(item, s) class EchonestMetadataPlugin(plugins.BeetsPlugin): diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py index a4e242026..6c20140d3 100644 --- a/beetsplug/mbsync.py +++ b/beetsplug/mbsync.py @@ -18,7 +18,6 @@ from beets.plugins import BeetsPlugin from beets import autotag, library, ui, util from beets.autotag import hooks from beets import config -from beets.util.functemplate import Template from collections import defaultdict @@ -71,10 +70,8 @@ class MBSyncPlugin(BeetsPlugin): """Retrieve and apply info from the autotagger for items matched by query. """ - template = Template(ui._pick_format(False, fmt)) - for item in lib.items(query + ['singleton:true']): - item_formatted = item.evaluate_template(template) + item_formatted = ui.format_(item, fmt) if not item.mb_trackid: self._log.info(u'Skipping singleton with no mb_trackid: {0}', item_formatted) @@ -97,11 +94,9 @@ class MBSyncPlugin(BeetsPlugin): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ - template = Template(ui._pick_format(True, fmt)) - # Process matching albums. for a in lib.albums(query): - album_formatted = a.evaluate_template(template) + album_formatted = ui.format_(a, fmt) if not a.mb_albumid: self._log.info(u'Skipping album with no mb_albumid: {0}', album_formatted) diff --git a/beetsplug/missing.py b/beetsplug/missing.py index a27be65d1..758669286 100644 --- a/beetsplug/missing.py +++ b/beetsplug/missing.py @@ -125,11 +125,11 @@ class MissingPlugin(BeetsPlugin): if count: missing = _missing_count(album) if missing: - print_obj(album, lib, fmt=fmt) + print_obj(album, fmt) else: for item in self._missing(album): - print_obj(item, lib, fmt=fmt) + print_obj(item, fmt) self._command.func = _miss return [self._command] diff --git a/beetsplug/random.py b/beetsplug/random.py index 2c4d0c000..668f08b2f 100644 --- a/beetsplug/random.py +++ b/beetsplug/random.py @@ -66,7 +66,7 @@ def random_item(lib, opts, args): objs = random.sample(objs, number) for item in objs: - print_obj(item, lib, template) + print_obj(item, template) random_cmd = Subcommand('random', help='chose a random track or album') From 8165dec9858d234e592e8a6d9c825f5e1c321e57 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Sun, 25 Jan 2015 21:32:22 +0100 Subject: [PATCH 3/7] Delete manual formattings of album & item --- beets/ui/commands.py | 2 +- beetsplug/echonest.py | 7 +++---- beetsplug/embedart.py | 14 ++++++-------- beetsplug/fetchart.py | 2 +- beetsplug/lastgenre/__init__.py | 8 ++++---- beetsplug/lyrics.py | 7 +++---- beetsplug/replaygain.py | 25 +++++++++---------------- 7 files changed, 27 insertions(+), 38 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 8fccc47a8..1014db80e 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -251,7 +251,7 @@ def show_change(cur_artist, cur_album, match): print_("To:") show_album(artist_r, album_r) else: - print_(u"Tagging:\n {0.artist} - {0.album}".format(match.info)) + print_(u"Tagging:\n {0}".format(match.info)) # Data URL. if match.info.data_url: diff --git a/beetsplug/echonest.py b/beetsplug/echonest.py index 817b96739..4de578916 100644 --- a/beetsplug/echonest.py +++ b/beetsplug/echonest.py @@ -401,10 +401,9 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): for method in methods: song = method(item) if song: - self._log.debug(u'got song through {0}: {1} - {2} [{3}]', + self._log.debug(u'got song through {0}: {1} [{2}]', method.__name__, - item.artist, - item.title, + item, song.get('duration'), ) return song @@ -471,7 +470,7 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): self.config.set_args(opts) write = config['import']['write'].get(bool) for item in lib.items(ui.decargs(args)): - self._log.info(u'{0} - {1}', item.artist, item.title) + self._log.info(u'{0}', item) if self.config['force'] or self.requires_update(item): song = self.fetch_song(item) if song: diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py index 4609f9f50..eb9d0e493 100644 --- a/beetsplug/embedart.py +++ b/beetsplug/embedart.py @@ -146,8 +146,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): """ imagepath = album.artpath if not imagepath: - self._log.info(u'No album art present: {0} - {1}', - album.albumartist, album.album) + self._log.info(u'No album art present: {0}', album) return if not os.path.isfile(syspath(imagepath)): self._log.error(u'Album art not found at {0}', @@ -158,7 +157,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): self._log.log( logging.DEBUG if quiet else logging.INFO, - u'Embedding album art into {0.albumartist} - {0.album}.', album + u'Embedding album art into {0}.', album ) for item in album.items(): @@ -253,8 +252,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): art = self.get_art(item) if not art: - self._log.error(u'No album art present in {0} - {1}.', - item.artist, item.title) + self._log.error(u'No album art present in {0}.', item) return # Add an extension to the filename. @@ -264,8 +262,8 @@ class EmbedCoverArtPlugin(BeetsPlugin): return outpath += '.' + ext - self._log.info(u'Extracting album art from: {0.artist} - {0.title} ' - u'to: {1}', item, displayable_path(outpath)) + self._log.info(u'Extracting album art from: {0} to: {1}', + item, displayable_path(outpath)) with open(syspath(outpath), 'wb') as f: f.write(art) return outpath @@ -274,7 +272,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): def clear(self, lib, query): self._log.info(u'Clearing album art from items:') for item in lib.items(query): - self._log.info(u'{0} - {1}', item.artist, item.title) + self._log.info(u'{0}', item) try: mf = mediafile.MediaFile(syspath(item.path), config['id3v23'].get(bool)) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index d86d942ee..59c2eaef6 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -375,7 +375,7 @@ class FetchArtPlugin(plugins.BeetsPlugin): else: message = ui.colorize('red', 'no art found') - self._log.info(u'{0.albumartist} - {0.album}: {1}', album, message) + self._log.info(u'{0}: {1}', album, message) def _source_urls(self, album): """Generate possible source URLs for an album's art. The URLs are diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 54604ece6..727ab08fe 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -337,8 +337,8 @@ class LastGenrePlugin(plugins.BeetsPlugin): for album in lib.albums(ui.decargs(args)): album.genre, src = self._get_genre(album) - self._log.info(u'genre for album {0.albumartist} - {0.album} ' - u'({1}): {0.genre}', album, src) + self._log.info(u'genre for album {0} ({1}): {0.genre}', + album, src) album.store() for item in album.items(): @@ -347,8 +347,8 @@ class LastGenrePlugin(plugins.BeetsPlugin): if 'track' in self.sources: item.genre, src = self._get_genre(item) item.store() - self._log.info(u'genre for track {0.artist} - {0.tit' - u'le} ({1}): {0.genre}', item, src) + self._log.info(u'genre for track {0} ({1}): {0.genre}', + item, src) if write: item.try_write() diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 5e51b2b7c..9dd1fce34 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -508,8 +508,7 @@ class LyricsPlugin(plugins.BeetsPlugin): lyrics will also be written to the file itself.""" # Skip if the item already has lyrics. if not force and item.lyrics: - self._log.info(u'lyrics already present: {0.artist} - {0.title}', - item) + self._log.info(u'lyrics already present: {0}', item) return lyrics = None @@ -521,9 +520,9 @@ class LyricsPlugin(plugins.BeetsPlugin): lyrics = u"\n\n---\n\n".join([l for l in lyrics if l]) if lyrics: - self._log.info(u'fetched lyrics: {0.artist} - {0.title}', item) + self._log.info(u'fetched lyrics: {0}', item) else: - self._log.info(u'lyrics not found: {0.artist} - {0.title}', item) + self._log.info(u'lyrics not found: {0}', item) fallback = self.config['fallback'].get() if fallback: lyrics = fallback diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index c2e21cd92..ec3941d12 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -558,7 +558,7 @@ class AudioToolsBackend(Backend): :rtype: :class:`AlbumGain` """ - self._log.debug(u'Analysing album {0.albumartist} - {0.album}', album) + self._log.debug(u'Analysing album {0}', album) # The first item is taken and opened to get the sample rate to # initialize the replaygain object. The object is used for all the @@ -574,15 +574,13 @@ class AudioToolsBackend(Backend): track_gains.append( Gain(gain=rg_track_gain, peak=rg_track_peak) ) - self._log.debug(u'ReplayGain for track {0.artist} - {0.title}: ' - u'{1:.2f}, {2:.2f}', + self._log.debug(u'ReplayGain for track {0}: {1:.2f}, {2:.2f}', item, rg_track_gain, rg_track_peak) # After getting the values for all tracks, it's possible to get the # album values. rg_album_gain, rg_album_peak = rg.album_gain() - self._log.debug(u'ReplayGain for album {0.albumartist} - {0.album}: ' - u'{1:.2f}, {2:.2f}', + self._log.debug(u'ReplayGain for album {0}: {1:.2f}, {2:.2f}', album, rg_album_gain, rg_album_peak) return AlbumGain( @@ -674,20 +672,17 @@ class ReplayGainPlugin(BeetsPlugin): items, nothing is done. """ if not self.album_requires_gain(album): - self._log.info(u'Skipping album {0} - {1}', - album.albumartist, album.album) + self._log.info(u'Skipping album {0}', album) return - self._log.info(u'analyzing {0} - {1}', album.albumartist, album.album) + self._log.info(u'analyzing {0}', album) try: album_gain = self.backend_instance.compute_album_gain(album) if len(album_gain.track_gains) != len(album.items()): raise ReplayGainError( u"ReplayGain backend failed " - u"for some tracks in album {0} - {1}".format( - album.albumartist, album.album - ) + u"for some tracks in album {0}".format(album) ) self.store_album_gain(album, album_gain.album_gain) @@ -711,18 +706,16 @@ class ReplayGainPlugin(BeetsPlugin): in the item, nothing is done. """ if not self.track_requires_gain(item): - self._log.info(u'Skipping track {0.artist} - {0.title}', item) + self._log.info(u'Skipping track {0}', item) return - self._log.info(u'analyzing {0} - {1}', item.artist, item.title) + self._log.info(u'analyzing {0}', item) try: track_gains = self.backend_instance.compute_track_gain([item]) if len(track_gains) != 1: raise ReplayGainError( - u"ReplayGain backend failed for track {0} - {1}".format( - item.artist, item.title - ) + u"ReplayGain backend failed for track {0}".format(item) ) self.store_track_gain(item, track_gains[0]) From 065cb59ce792e776f34e51dbd3041720c6247e7d Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Mon, 26 Jan 2015 09:44:51 +0100 Subject: [PATCH 4/7] Restore show_change() logging: MatchInfo, not Item --- beets/ui/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 1014db80e..8fccc47a8 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -251,7 +251,7 @@ def show_change(cur_artist, cur_album, match): print_("To:") show_album(artist_r, album_r) else: - print_(u"Tagging:\n {0}".format(match.info)) + print_(u"Tagging:\n {0.artist} - {0.album}".format(match.info)) # Data URL. if match.info.data_url: From 3787f8a1ddcebd85718f1ea0c47547f03202a3a8 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Mon, 26 Jan 2015 09:46:12 +0100 Subject: [PATCH 5/7] Improve comments on formatting inner workings --- beets/library.py | 3 +++ beets/ui/__init__.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/beets/library.py b/beets/library.py index a391bfb51..4fa838312 100644 --- a/beets/library.py +++ b/beets/library.py @@ -229,6 +229,8 @@ class WriteError(FileOperationError): class LibModel(dbcore.Model): """Shared concrete functionality for Items and Albums. """ + _format_config_key = None + """Config key that specifies how an instance should be formatted""" def _template_funcs(self): funcs = DefaultTemplateFunctions(self, self._db).functions() @@ -252,6 +254,7 @@ class LibModel(dbcore.Model): spec = beets.config[self._format_config_key].get(unicode) result = self.evaluate_template(spec) if isinstance(spec, bytes): + # if spec is a byte string then we must return a one as well return result.encode('utf8') else: return result diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 0385584ac..1ace2f0b3 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -97,6 +97,9 @@ def print_(*strings): """Like print, but rather than raising an error when a character is not in the terminal's encoding's character set, just silently replaces it. + + If the arguments are strings then they're expected to share the same type: + either bytes or unicode. """ if strings: if not isinstance(strings[0], basestring): From 4e904c78af8a6e60c3390b41b9aa5a31272a725b Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Mon, 26 Jan 2015 23:09:56 +0100 Subject: [PATCH 6/7] Simplify LibModel format management Delete `ui.format_` and then `ui.print_obj`. Simply ensure that when there is no format it defaults to '' = default format = config option. --- beets/ui/__init__.py | 19 ------------------- beets/ui/commands.py | 19 ++++++++----------- beetsplug/convert.py | 2 +- beetsplug/duplicates.py | 8 ++++---- beetsplug/echonest.py | 2 +- beetsplug/mbsync.py | 6 +++--- beetsplug/missing.py | 11 +++++------ beetsplug/random.py | 13 ++++--------- test/test_ui.py | 2 +- 9 files changed, 27 insertions(+), 55 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 1ace2f0b3..956eae41a 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -480,25 +480,6 @@ def get_replacements(): return replacements -def format_(obj, fmt): - """Print a object, intended for Album and Item - - This is equivalent to `format`, but the spec can be None - `fmt` is mandatory for otherwise one can just call `format(my_object)`""" - if fmt: - return format(obj, fmt) - else: - return format(obj) - - -def print_obj(obj, fmt): - """Print a object, intended for Album and Item - - This is equivalent to `print_ o format`, but the spec can be None - `fmt` is mandatory for otherwise one can just call `print_(my_object)`""" - return print_(format_(obj, fmt)) - - def term_width(): """Get the width (columns) of the terminal.""" fallback = config['ui']['terminal_width'].get(int) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 8fccc47a8..4f2c017dc 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -952,17 +952,14 @@ def list_items(lib, query, album, fmt): """ if album: for album in lib.albums(query): - ui.print_obj(album, fmt) + ui.print_(format(album, fmt)) else: for item in lib.items(query): - ui.print_obj(item, fmt) + ui.print_(format(item, fmt)) def list_func(lib, opts, args): - if opts.path: - fmt = '$path' - else: - fmt = opts.format + fmt = '$path' if opts.path else opts.format list_items(lib, decargs(args), opts.album, fmt) @@ -977,7 +974,7 @@ list_cmd.parser.add_option( ) list_cmd.parser.add_option( '-f', '--format', action='store', - help='print with custom format', default=None + help='print with custom format', default='' ) list_cmd.func = list_func default_commands.append(list_cmd) @@ -1093,7 +1090,7 @@ update_cmd.parser.add_option( ) update_cmd.parser.add_option( '-f', '--format', action='store', - help='print with custom format', default=None + help='print with custom format', default='' ) update_cmd.func = update_func default_commands.append(update_cmd) @@ -1114,13 +1111,13 @@ def remove_items(lib, query, album, delete): fmt = u'$path - $title' prompt = 'Really DELETE %i files (y/n)?' % len(items) else: - fmt = None + fmt = '' prompt = 'Really remove %i items from the library (y/n)?' % \ len(items) # Show all the items. for item in items: - ui.print_obj(item, fmt) + ui.print_(format(item, fmt)) # Confirm with user. if not ui.input_yn(prompt, True): @@ -1350,7 +1347,7 @@ modify_cmd.parser.add_option( ) modify_cmd.parser.add_option( '-f', '--format', action='store', - help='print with custom format', default=None + help='print with custom format', default='' ) modify_cmd.func = modify_func default_commands.append(modify_cmd) diff --git a/beetsplug/convert.py b/beetsplug/convert.py index baf084423..4274448ad 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -356,7 +356,7 @@ class ConvertPlugin(BeetsPlugin): self.config['pretend'].get(bool) if not pretend: - ui.commands.list_items(lib, ui.decargs(args), opts.album, None) + ui.commands.list_items(lib, ui.decargs(args), opts.album, '') if not (opts.yes or ui.input_yn("Convert? (Y/n)")): return diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index ea235295c..ac5b19c17 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -17,14 +17,14 @@ import shlex from beets.plugins import BeetsPlugin -from beets.ui import decargs, print_obj, vararg_callback, Subcommand, UserError +from beets.ui import decargs, print_, vararg_callback, Subcommand, UserError from beets.util import command_output, displayable_path, subprocess PLUGIN = 'duplicates' def _process_item(item, lib, copy=False, move=False, delete=False, - tag=False, format=None): + tag=False, format=''): """Process Item `item` in `lib`. """ if copy: @@ -42,7 +42,7 @@ def _process_item(item, lib, copy=False, move=False, delete=False, raise UserError('%s: can\'t parse k=v tag: %s' % (PLUGIN, tag)) setattr(k, v) item.store() - print_obj(item, format) + print_(format(item, format)) def _checksum(item, prog, log): @@ -126,7 +126,7 @@ class DuplicatesPlugin(BeetsPlugin): self._command.parser.add_option('-f', '--format', dest='format', action='store', type='string', help='print with custom format', - metavar='FMT') + metavar='FMT', default='') self._command.parser.add_option('-a', '--album', dest='album', action='store_true', diff --git a/beetsplug/echonest.py b/beetsplug/echonest.py index 4de578916..fc66ad775 100644 --- a/beetsplug/echonest.py +++ b/beetsplug/echonest.py @@ -115,7 +115,7 @@ def similar(lib, src_item, threshold=0.15, fmt='${difference}: ${path}'): d = diff(item, src_item) if d < threshold: s = fmt.replace('${difference}', '{:2.2f}'.format(d)) - ui.print_obj(item, s) + ui.print_(format(item, s)) class EchonestMetadataPlugin(plugins.BeetsPlugin): diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py index 6c20140d3..aea50fd15 100644 --- a/beetsplug/mbsync.py +++ b/beetsplug/mbsync.py @@ -49,7 +49,7 @@ class MBSyncPlugin(BeetsPlugin): cmd.parser.add_option('-W', '--nowrite', action='store_false', default=config['import']['write'], dest='write', help="don't write updated metadata to files") - cmd.parser.add_option('-f', '--format', action='store', default=None, + cmd.parser.add_option('-f', '--format', action='store', default='', help='print with custom format') cmd.func = self.func return [cmd] @@ -71,7 +71,7 @@ class MBSyncPlugin(BeetsPlugin): query. """ for item in lib.items(query + ['singleton:true']): - item_formatted = ui.format_(item, fmt) + item_formatted = format(item, fmt) if not item.mb_trackid: self._log.info(u'Skipping singleton with no mb_trackid: {0}', item_formatted) @@ -96,7 +96,7 @@ class MBSyncPlugin(BeetsPlugin): """ # Process matching albums. for a in lib.albums(query): - album_formatted = ui.format_(a, fmt) + album_formatted = format(a, fmt) if not a.mb_albumid: self._log.info(u'Skipping album with no mb_albumid: {0}', album_formatted) diff --git a/beetsplug/missing.py b/beetsplug/missing.py index 8abcdc125..1748c731d 100644 --- a/beetsplug/missing.py +++ b/beetsplug/missing.py @@ -17,7 +17,7 @@ from beets.autotag import hooks from beets.library import Item from beets.plugins import BeetsPlugin -from beets.ui import decargs, print_obj, Subcommand +from beets.ui import decargs, print_, Subcommand def _missing_count(album): @@ -95,7 +95,7 @@ class MissingPlugin(BeetsPlugin): self._command.parser.add_option('-f', '--format', dest='format', action='store', type='string', help='print with custom FORMAT', - metavar='FORMAT') + metavar='FORMAT', default='') self._command.parser.add_option('-c', '--count', dest='count', action='store_true', @@ -123,13 +123,12 @@ class MissingPlugin(BeetsPlugin): for album in albums: if count: - missing = _missing_count(album) - if missing: - print_obj(album, fmt) + if _missing_count(album): + print_(format(album, fmt)) else: for item in self._missing(album): - print_obj(item, fmt) + print_(format(item, fmt)) self._command.func = _miss return [self._command] diff --git a/beetsplug/random.py b/beetsplug/random.py index 668f08b2f..fefe46aaf 100644 --- a/beetsplug/random.py +++ b/beetsplug/random.py @@ -16,8 +16,7 @@ """ from __future__ import absolute_import from beets.plugins import BeetsPlugin -from beets.ui import Subcommand, decargs, print_obj -from beets.util.functemplate import Template +from beets.ui import Subcommand, decargs, print_ import random from operator import attrgetter from itertools import groupby @@ -25,11 +24,7 @@ from itertools import groupby def random_item(lib, opts, args): query = decargs(args) - if opts.path: - fmt = '$path' - else: - fmt = opts.format - template = Template(fmt) if fmt else None + fmt = '$path' if opts.path else opts.format if opts.album: objs = list(lib.albums(query)) @@ -66,7 +61,7 @@ def random_item(lib, opts, args): objs = random.sample(objs, number) for item in objs: - print_obj(item, template) + print_(format(item, fmt)) random_cmd = Subcommand('random', help='chose a random track or album') @@ -75,7 +70,7 @@ random_cmd.parser.add_option('-a', '--album', action='store_true', random_cmd.parser.add_option('-p', '--path', action='store_true', help='print the path of the matched item') random_cmd.parser.add_option('-f', '--format', action='store', - help='print with custom format', default=None) + help='print with custom format', default='') random_cmd.parser.add_option('-n', '--number', action='store', type="int", help='number of objects to choose', default=1) random_cmd.parser.add_option('-e', '--equal-chance', action='store_true', diff --git a/test/test_ui.py b/test/test_ui.py index e32f9ed83..162694565 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -43,7 +43,7 @@ class ListTest(unittest.TestCase): self.lib.add(self.item) self.lib.add_album([self.item]) - def _run_list(self, query='', album=False, path=False, fmt=None): + def _run_list(self, query='', album=False, path=False, fmt=''): commands.list_items(self.lib, query, album, fmt) def test_list_outputs_item(self): From 4aba4320e62b79f48d64449a9361885b440711af Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Mon, 26 Jan 2015 23:47:12 +0100 Subject: [PATCH 7/7] Rollback ui.print_() auto-conv of args to string Since this raises problems the best is probably to maintain the base behaviour: expect byte strings or unicodes. --- beets/ui/__init__.py | 8 +------- beets/ui/commands.py | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 956eae41a..a6b7e5518 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -102,12 +102,6 @@ def print_(*strings): either bytes or unicode. """ if strings: - if not isinstance(strings[0], basestring): - try: - strings = map(unicode, strings) - except UnicodeError: - strings = map(bytes, strings) - if isinstance(strings[0], unicode): txt = u' '.join(strings) else: @@ -571,7 +565,7 @@ def show_model_changes(new, old=None, fields=None, always=False): # Print changes. if changes or always: - print_(old) + print_(format(old)) if changes: print_(u'\n'.join(changes)) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 4f2c017dc..34e8b7518 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -994,7 +994,7 @@ def update_items(lib, query, album, move, pretend): for item in items: # Item deleted? if not os.path.exists(syspath(item.path)): - ui.print_(item) + ui.print_(format(item)) ui.print_(ui.colorize('red', u' deleted')) if not pretend: item.remove(True)