diff --git a/beetsplug/info.py b/beetsplug/info.py index d6988d097..5f68336e9 100644 --- a/beetsplug/info.py +++ b/beetsplug/info.py @@ -25,6 +25,7 @@ import re from beets.plugins import BeetsPlugin from beets import ui from beets import mediafile +from beets.library import Item from beets.util import displayable_path, normpath, syspath @@ -51,8 +52,10 @@ def tag_data_emitter(path): for field in fields: tags[field] = getattr(mf, field) tags['art'] = mf.art is not None - tags['path'] = displayable_path(path) - return tags + # create a temporary Item to take advantage of __format__ + item = Item.from_path(syspath(path)) + + return tags, item return emitter @@ -64,8 +67,9 @@ def library_data(lib, args): def library_data_emitter(item): def emitter(): data = dict(item.formatted()) - data['path'] = displayable_path(item.path) - return data + data.pop('path', None) # path is fetched from item + + return data, item return emitter @@ -78,8 +82,20 @@ def update_summary(summary, tags): return summary -def print_data(data): - path = data.pop('path', None) +def print_data(data, item=None, fmt=None): + """Print, with optional formatting, the fields of a single element. + + If no format string `fmt` is passed, the entries on `data` are printed one + in each line, with the format 'field: value'. If `fmt` is not `None`, the + `item` is printed according to `fmt`, using the `Item.__format__` + machinery. + """ + if fmt: + # use fmt specified by the user + ui.print_(format(item, fmt)) + return + + path = displayable_path(item.path) if item else None formatted = {} for key, value in data.iteritems(): if isinstance(value, list): @@ -115,6 +131,7 @@ class InfoPlugin(BeetsPlugin): cmd.parser.add_option('-i', '--include-keys', default=[], action='append', dest='included_keys', help='comma separated list of keys to show') + cmd.parser.add_format_option(target='item') return [cmd] def run(self, lib, opts, args): @@ -145,20 +162,18 @@ class InfoPlugin(BeetsPlugin): summary = {} for data_emitter in data_collector(lib, ui.decargs(args)): try: - data = data_emitter() + data, item = data_emitter() except (mediafile.UnreadableFileError, IOError) as ex: self._log.error(u'cannot read file: {0}', ex) continue - path = data.get('path') data = key_filter(data) - data['path'] = path # always show path if opts.summarize: update_summary(summary, data) else: if not first: ui.print_() - print_data(data) + print_data(data, item, opts.format) first = False if opts.summarize: @@ -181,14 +196,14 @@ def make_key_filter(include): key = key.replace(r'\*', '.*') matchers.append(re.compile(key + '$')) - def filter(data): + def filter_(data): filtered = dict() for key, value in data.items(): if any(map(lambda m: m.match(key), matchers)): filtered[key] = value return filtered - return filter + return filter_ def identity(val): diff --git a/docs/changelog.rst b/docs/changelog.rst index 54c70f370..ac6ded79c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -24,6 +24,8 @@ New: *exclude* matching music from the results. For example, ``beet list -a beatles ^album:1`` will find all your albums by the Beatles except for their singles compilation, "1." See :ref:`not_query`. :bug:`819` :bug:`1728` +* :doc:`/plugins/info`: The plugin now accepts the ``-f/--format`` option for + customizing how items are displayed. :bug:`1737` For developers: diff --git a/docs/plugins/info.rst b/docs/plugins/info.rst index 5bfde0b41..b36e69051 100644 --- a/docs/plugins/info.rst +++ b/docs/plugins/info.rst @@ -36,6 +36,10 @@ Additional command-line options include: * ``--summarize`` or ``-s``: Merge all the information from multiple files into a single list of values. If the tags differ across the files, print ``[various]``. +* ``--format`` or ``-f``: Specify a specific format with which to print every + item. This uses the same template syntax as beets’ :doc:`path formats + `. + .. _id3v2: http://id3v2.sourceforge.net .. _mp3info: http://www.ibiblio.org/mp3info/ diff --git a/test/test_info.py b/test/test_info.py index 797950be3..aaabed980 100644 --- a/test/test_info.py +++ b/test/test_info.py @@ -52,6 +52,7 @@ class InfoTest(unittest.TestCase, TestHelper): self.assertIn('disctitle: DDD', out) self.assertIn('genres: a; b; c', out) self.assertNotIn('composer:', out) + self.remove_mediafile_fixtures() def test_item_query(self): item1, item2 = self.add_item_fixtures(count=2) @@ -93,6 +94,7 @@ class InfoTest(unittest.TestCase, TestHelper): self.assertIn(u'album: AAA', out) self.assertIn(u'tracktotal: 5', out) self.assertIn(u'title: [various]', out) + self.remove_mediafile_fixtures() def test_include_pattern(self): item, = self.add_item_fixtures() @@ -105,6 +107,12 @@ class InfoTest(unittest.TestCase, TestHelper): self.assertNotIn(u'title:', out) self.assertIn(u'album: xxxx', out) + def test_custom_format(self): + self.add_item_fixtures() + out = self.run_with_output('--library', '--format', + '$track. $title - $artist ($length)') + self.assertEqual(u'02. tïtle 0 - the artist (1.1)\n', out) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__)