X-Git-Url: http://git.tremily.us/?p=hooke.git;a=blobdiff_plain;f=hooke%2Fplugin%2F__init__.py;h=d04f3740f3797fae36e731a0d01de3a43d56622f;hp=0770f9e6f815fbaa81ea361fb6f1eb5a569b7993;hb=b90995fb4b6d8151df862d40edc8c369d7052cfa;hpb=51684c8eaa07dc68c4e074d853a801a0ab083989 diff --git a/hooke/plugin/__init__.py b/hooke/plugin/__init__.py index 0770f9e..d04f374 100644 --- a/hooke/plugin/__init__.py +++ b/hooke/plugin/__init__.py @@ -1,92 +1,191 @@ -#!/usr/bin/env python -"""The plugin module provides optional submodules that add new Hooke +# Copyright (C) 2010 W. Trevor King +# +# This file is part of Hooke. +# +# Hooke is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Hooke is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Hooke. If not, see +# . + +"""The `plugin` module provides optional submodules that add new Hooke commands. All of the science happens in here. """ -import os.path +import ConfigParser as configparser +import logging from ..config import Setting -from ..util.graph import Node, Graph +from ..util.pluggable import IsSubclass, construct_graph + PLUGIN_MODULES = [ - ('autopeak', True), - ('curvetools', True), +# ('autopeak', True), + ('convfilt', True), ('cut', True), - ('fit', True), - ('flatfilts-rolf', True), - ('flatfilts', True), - ('generalclamp', True), - ('generaltccd', True), - ('generalvclamp', True), - ('jumpstat', True), - ('macro', True), - ('massanalysis', True), - ('multidistance', True), - ('multifit', True), - ('pcluster', True), - ('procplots', True), - ('review', True), - ('showconvoluted', True), - ('superimpose', True), +# ('fclamp', True), +# ('flatfilts-rolf', True), + ('flatfilt', True), +# ('jumpstat', True), +# ('massanalysis', True), +# ('multidistance', True), +# ('multifit', True), +# ('pcluster', True), + ('polymer_fit', True), +# ('procplots', True), +# ('review', True), +# ('showconvoluted', True), +# ('superimpose', True), +# ('tccd', True), ('tutorial', True), - ('viewer', True), + ('vclamp', True), ] """List of plugin modules and whether they should be included by -default. +default. TODO: autodiscovery +""" + +BUILTIN_MODULES = [ + 'command_stack', + 'config', + 'curve', + 'debug', + 'engine', + 'license', + 'note', + 'playlist', + 'playlists', + 'system', + ] +"""List of builtin modules. TODO: autodiscovery +""" + +PLUGIN_SETTING_SECTION = 'plugins' +"""Name of the config section which controls plugin selection. """ -def Plugin (object): - """The pluggable collection of Hooke commands. + +# Plugins and settings + +class Plugin (object): + """A pluggable collection of Hooke commands. Fulfills the same role for Hooke that a software package does for - an operating system. + an operating system: contains a chunk of related commands and + routines. Command configuration also happens on the `Plugin` + level, with per-plugin sections in the configuration file. """ - name = "base plugin" + def __init__(self, name): + self.name = name + self.setting_section = '%s plugin' % self.name + self.config = {} + self._commands = [] def dependencies(self): - """Return a list of Plugins we require.""" + """Return a list of names of :class:`Plugin`\s we require.""" return [] def default_settings(self): - """Return a list of hooke.config.Settings() for any - configurable module settings.""" + """Return a list of :class:`hooke.config.Setting`\s for any + configurable plugin settings. + + The suggested section setting is:: + + Setting(section=self.setting_section, help=self.__doc__) + """ return [] def commands(self): - """Return a list of Commands provided.""" - return [] + """Return a list of :class:`hooke.command.Command`\s provided. + """ + return list(self._commands) + + +class Builtin (Plugin): + """A required collection of Hooke commands. -PLUGINS = {} -"""(name,instance) :class:`dict` of all possible :class:`Plugin`\s. + These "core" plugins provide essential administrative commands + (playlist handling, etc.). + """ + pass + + +# Plugin utility functions + +def argument_to_setting(section_name, argument): + """Convert an :class:`~hooke.command.Argument` to a + `~hooke.conf.Setting`. + + This is useful if, for example, you want to define arguments with + configurable default values. + + Conversion is lossy transition, because + :class:`~hooke.command.Argument`\s store more information than + `~hooke.conf.Setting`\s. + """ + return Setting(section_name, option=argument.name, value=argument.default, + type=argument.type, count=argument.count, + help=argument._help) + + +# Construct plugin dependency graph and load plugin instances. + +PLUGIN_GRAPH = construct_graph( + this_modname=__name__, + submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES, + class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin])) +"""Topologically sorted list of all possible :class:`Plugin`\s and +:class:`Builtin`\s. """ -print __name__ -for plugin_modname,value in PLUGIN_MODULES: - this_mod = __import__(__name__, fromlist=[plugin_modname]) - plugin_mod = getattr(this_mod, plugin_modname) - for objname in dir(plugin_mod): - obj = getattr(plugin_mod, objname) - if type(obj) == Plugin: - obj.module_name = plugin_modname - PLUGINS[p.name] = p - -PLUGIN_GRAPH = Graph([Node( - [PLUGINS[name] for name in p.dependencies()] - )]) -PLUGIN_GRAPH.topological_sort() - - -def default_settings(self): - settings = [Setting( - 'plugins', help='Enable/disable default plugins.')] +def default_settings(): + settings = [Setting(PLUGIN_SETTING_SECTION, + help='Enable/disable default plugins.')] for pnode in PLUGIN_GRAPH: - settings.append(Setting(p.name, str(PLUGIN_MODULES[p.module_name][1]))) + if pnode.data.name in BUILTIN_MODULES: + continue # builtin inclusion is not optional + plugin = pnode.data + default_include = [di for mod_name,di in PLUGIN_MODULES + if mod_name == plugin.name][0] + help = 'Commands: ' + ', '.join([c.name for c in plugin.commands()]) + settings.append(Setting( + section=PLUGIN_SETTING_SECTION, + option=plugin.name, + value=str(default_include), + help=help, + )) for pnode in PLUGIN_GRAPH: plugin = pnode.data settings.extend(plugin.default_settings()) return settings -class Command (object): - pass +def load_graph(graph, config, include_section): + enabled = {} + items = [] + for node in graph: + item = node.data + try: + include = config.getboolean(include_section, item.name) + except configparser.NoOptionError: + include = True # non-optional include (e.g. a Builtin) + enabled[item.name] = include + if include == True: + for dependency in node: + if enabled.get(dependency.data.name, None) != True: + log = logging.getLogger('hooke') + log.warn( + 'could not setup plugin %s. unsatisfied dependency on %s.' + % (item.name, dependency.data.name)) + enabled[item.name] = False + continue + items.append(item) + return items