From 78efe771b19c3fed139d91af1f5a92195679b249 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 8 Jul 2010 16:35:15 -0700 Subject: [PATCH] extremely simple plugin system with discovery --- beets/plugins.py | 73 +++++++++++++++++++++++++++++++++++++++++++ beets/ui/__init__.py | 6 +++- beets/ui/commands.py | 1 + beetsplug/__init__.py | 0 setup.py | 4 ++- 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 beets/plugins.py create mode 100644 beetsplug/__init__.py diff --git a/beets/plugins.py b/beets/plugins.py new file mode 100644 index 000000000..ebed3b271 --- /dev/null +++ b/beets/plugins.py @@ -0,0 +1,73 @@ +# This file is part of beets. +# Copyright 2010, Adrian Sampson. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Support for beets plugins.""" + +import logging +import itertools + +# Global logger. +log = logging.getLogger('beets') + +PLUGIN_NAMESPACE = 'beetsplug' +DEFAULT_PLUGINS = [] + +class BeetsPlugin(object): + """The base class for all beets plugins. Plugins provide + functionality by defining a subclass of BeetsPlugin and overriding + the abstract methods defined here. + """ + def commands(self): + """Should return a list of beets.ui.Subcommand objects for + commands that should be added to beets' CLI. + """ + raise NotImplementedError + +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 + package in sys.path; the module indicated should contain the + BeetsPlugin subclasses desired. A default set of plugins is also + loaded. + """ + for name in itertools.chain(names, DEFAULT_PLUGINS): + modname = '%s.%s' % (PLUGIN_NAMESPACE, name) + try: + __import__(modname, None, None) + except: + log.warn('plugin %s not found' % name) + +_instances = {} +def find_plugins(): + """Returns a list of BeetsPlugin subclass instances from all + currently loaded beets plugins. Loads the default plugin set + first. + """ + load_plugins() + plugins = [] + for cls in BeetsPlugin.__subclasses__(): + # Only instantiate each plugin class once. + if cls not in _instances: + _instances[cls] = cls() + plugins.append(_instances[cls]) + return plugins + +def commands(): + """Returns a list of Subcommand objects from all loaded plugins. + """ + out = [] + for plugin in find_plugins(): + out += plugin.commands() + return out + diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 9df7e8304..50d34ea73 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -24,6 +24,7 @@ import textwrap import ConfigParser from beets import library +from beets import plugins # Constants. CONFIG_FILE = os.path.expanduser('~/.beetsconfig') @@ -340,7 +341,9 @@ def main(): from beets.ui.commands import default_commands # Construct the root parser. - parser = SubcommandsOptionParser(subcommands=default_commands) + commands = list(default_commands) + commands += plugins.commands() + parser = SubcommandsOptionParser(subcommands=commands) parser.add_option('-l', '--library', dest='libpath', help='library database file to use') parser.add_option('-d', '--directory', dest='directory', @@ -378,3 +381,4 @@ def main(): except UserError, exc: message = exc.args[0] if exc.args else None subcommand.parser.error(message) + diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 7bfc717ea..7962479e5 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -505,3 +505,4 @@ def stats_func(lib, config, opts, args): show_stats(lib, ui.make_query(args)) stats_cmd.func = stats_func default_commands.append(stats_cmd) + diff --git a/beetsplug/__init__.py b/beetsplug/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/setup.py b/setup.py index 33cf55e30..3b2d0cdf8 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def _read(fn): return open(path).read() setup(name='beets', - version='1.0b2', + version='1.0b3', description='music tagger and library organizer', author='Adrian Sampson', author_email='adrian@radbox.org', @@ -37,7 +37,9 @@ setup(name='beets', 'beets.ui', 'beets.autotag', 'beets.player', + 'beetsplug', ], + namespace_packages=['beetsplug'], scripts=['beet'], install_requires=[