From f1ebc82a5512a13b6841fda67a1218b129e73067 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Fri, 16 Dec 2011 11:56:40 -0800 Subject: [PATCH] plugin hooks for template functions (#231) --- beets/library.py | 4 +++- beets/plugins.py | 25 +++++++++++++++++++++ beets/ui/__init__.py | 6 ++--- docs/plugins/index.rst | 50 ++++++++++++++++++++++++++++++++---------- 4 files changed, 69 insertions(+), 16 deletions(-) diff --git a/beets/library.py b/beets/library.py index 38f6de142..5c4cfc841 100644 --- a/beets/library.py +++ b/beets/library.py @@ -817,7 +817,9 @@ class Library(BaseLibrary): mapping['albumartist'] = mapping['artist'] # Perform substitution. - subpath = subpath_tmpl.substitute(mapping, TEMPLATE_FUNCTIONS) + funcs = dict(TEMPLATE_FUNCTIONS) + funcs.update(plugins.template_funcs()) + subpath = subpath_tmpl.substitute(mapping, funcs) # Encode for the filesystem, dropping unencodable characters. if isinstance(subpath, unicode) and not fragment: diff --git a/beets/plugins.py b/beets/plugins.py index fffec7642..583b95f88 100755 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -104,6 +104,21 @@ class BeetsPlugin(object): return func return helper + template_funcs = None + + @classmethod + def template_func(cls, name): + """Decorator that registers a path template function. The + function will be invoked as ``%name{}`` from path format + strings. + """ + def helper(func): + if cls.template_funcs is None: + cls.template_funcs = {} + cls.template_funcs[name] = func + return func + return helper + def load_plugins(names=()): """Imports the modules for a sequence of plugin names. Each name must be the name of a Python module under the "beetsplug" namespace @@ -195,6 +210,16 @@ def configure(config): for plugin in find_plugins(): plugin.configure(config) +def template_funcs(): + """Get all the template functions declared by plugins as a + dictionary. + """ + funcs = {} + for plugin in find_plugins(): + if plugin.template_funcs: + funcs.update(plugin.template_funcs) + return funcs + # Event dispatch. diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 8777cdc7a..8dd2964e8 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -274,10 +274,10 @@ def config_val(config, section, name, default, vtype=None): return config.getboolean(section, name) elif vtype is list: # Whitespace-separated strings. - strval = config.get(section, name) + strval = config.get(section, name, True) return strval.split() else: - return config.get(section, name) + return config.get(section, name, True) except ConfigParser.NoOptionError: return default @@ -637,7 +637,7 @@ def main(args=None, configfh=None): # If no legacy path format, use the defaults instead. path_formats = DEFAULT_PATH_FORMATS if config.has_section('paths'): - path_formats.update(config.items('paths')) + path_formats.update(config.items('paths', True)) art_filename = \ config_val(config, 'beets', 'art_filename', DEFAULT_ART_FILENAME) lib_timeout = config_val(config, 'beets', 'timeout', DEFAULT_TIMEOUT) diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 012809f96..a0ff63ff5 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -1,10 +1,9 @@ Plugins ======= -As of the 1.0b3 release, beets started supporting plugins to modularize its -functionality and allow other developers to add new functionality. Plugins can -add new commands to the command-line interface, respond to events in beets, and -augment the autotagger. +Plugins can extend beets' core functionality. Plugins can add new commands to +the command-line interface, respond to events in beets, augment the autotagger, +or provide new path template functions. Using Plugins ------------- @@ -169,10 +168,10 @@ like you would a normal ``OptionParser`` in an independent script. Listen for Events ^^^^^^^^^^^^^^^^^ -As of beets 1.0b5, plugins can also define event handlers. Event handlers allow -you to run code whenever something happens in beets' operation. For instance, a -plugin could write a log message every time an album is successfully autotagged -or update MPD's index whenever the database is changed. +Event handlers allow plugins to run code whenever something happens in beets' +operation. For instance, a plugin could write a log message every time an album +is successfully autotagged or update MPD's index whenever the database is +changed. You can "listen" for events using the ``BeetsPlugin.listen`` decorator. Here's an example:: @@ -210,7 +209,7 @@ The included ``mpdupdate`` plugin provides an example use case for event listene Extend the Autotagger ^^^^^^^^^^^^^^^^^^^^^ -Plugins in 1.0b5 can also enhance the functionality of the autotagger. For a +Plugins in can also enhance the functionality of the autotagger. For a comprehensive example, try looking at the ``chroma`` plugin, which is included with beets. @@ -223,19 +222,22 @@ methods on the plugin class: * ``track_distance(self, item, info)``: adds a component to the distance function (i.e., the similarity metric) for individual tracks. ``item`` is the - track to be matched (and Item object) and ``info`` is the !MusicBrainz track + track to be matched (and Item object) and ``info`` is the MusicBrainz track entry that is proposed as a match. Should return a ``(dist, dist_max)`` pair of floats indicating the distance. * ``album_distance(self, items, info)``: like the above, but compares a list of - items (representing an album) to an album-level !MusicBrainz entry. Should + items (representing an album) to an album-level MusicBrainz entry. Should only consider album-level metadata (e.g., the artist name and album title) and should not duplicate the factors considered by ``track_distance``. * ``candidates(self, items)``: given a list of items comprised by an album to be - matched, return a list of !MusicBrainz entries for candidate albums to be + matched, return a list of ``AlbumInfo`` objects for candidate albums to be compared and matched. +* ``item_candidates(self, item)``: given a *singleton* item, return a list of + ``TrackInfo`` objects for candidate tracks to be compared and matched. + When implementing these functions, it will probably be very necessary to use the functions from the ``beets.autotag`` and ``beets.autotag.mb`` modules, both of which have somewhat helpful docstrings. @@ -254,3 +256,27 @@ values from the config file. Like so:: Try looking at the ``mpdupdate`` plugin (included with beets) for an example of real-world use of this API. + +Add Path Format Functions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +As of 1.0b12, beets supports *function calls* in its path format syntax (see +:doc:`/reference/pathformat`. Beets includes a few built-in functions, but +plugins can add new functions using the ``template_func`` decorator. To use it, +decorate a function with ``MyPlugin.template_func("name")`` where ``name`` is +the name of the function as it should appear in template strings. + +Here's an example:: + + class MyPlugin(BeetsPlugin): + pass + @MyPlugin.template_func('initial') + def _tmpl_initial(text): + if text: + return text[0].upper() + else: + return u'' + +This plugin provides a function ``%initial`` to path templates where +``%initial{$artist}`` expands to the artist's initial (its capitalized first +character).