plugin hooks for template functions (#231)

This commit is contained in:
Adrian Sampson 2011-12-16 11:56:40 -08:00
parent dd4ee6b2e4
commit f1ebc82a55
4 changed files with 69 additions and 16 deletions

View file

@ -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:

View file

@ -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.

View file

@ -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)

View file

@ -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).