X-Git-Url: http://git.tremily.us/?p=hooke.git;a=blobdiff_plain;f=hooke%2Fplugin%2F__init__.py;h=d04f3740f3797fae36e731a0d01de3a43d56622f;hp=5a5954e5b09e8b200a6beab019b8925ffb634f02;hb=b90995fb4b6d8151df862d40edc8c369d7052cfa;hpb=f0288ee2a5623f462418dac238d796994da0b147 diff --git a/hooke/plugin/__init__.py b/hooke/plugin/__init__.py index 5a5954e..d04f374 100644 --- a/hooke/plugin/__init__.py +++ b/hooke/plugin/__init__.py @@ -1,3 +1,21 @@ +# 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. @@ -5,41 +23,48 @@ All of the science happens in here. """ 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), + ('convfilt', True), ('cut', True), - ('debug', True), -# ('fit', True), +# ('fclamp', True), # ('flatfilts-rolf', True), -# ('flatfilts', True), -# ('generalclamp', True), -# ('generaltccd', True), -# ('generalvclamp', True), + ('flatfilt', True), # ('jumpstat', True), -# ('macro', True), # ('massanalysis', True), # ('multidistance', True), # ('multifit', True), # ('pcluster', True), + ('polymer_fit', True), # ('procplots', True), # ('review', True), # ('showconvoluted', True), # ('superimpose', True), - ('system', True), -# ('tutorial', True), +# ('tccd', True), + ('tutorial', True), + ('vclamp', True), ] """List of plugin modules and whether they should be included by default. TODO: autodiscovery """ BUILTIN_MODULES = [ + 'command_stack', + 'config', + 'curve', + 'debug', + 'engine', + 'license', + 'note', 'playlist', + 'playlists', + 'system', ] """List of builtin modules. TODO: autodiscovery """ @@ -55,15 +80,18 @@ 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. """ 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 :class:`Plugin`\s we require.""" + """Return a list of names of :class:`Plugin`\s we require.""" return [] def default_settings(self): @@ -79,7 +107,8 @@ class Plugin (object): def commands(self): """Return a list of :class:`hooke.command.Command`\s provided. """ - return [] + return list(self._commands) + class Builtin (Plugin): """A required collection of Hooke commands. @@ -89,79 +118,26 @@ class Builtin (Plugin): """ pass -# Construct plugin dependency graph and load plugin instances. -def construct_graph(this_modname, submodnames, class_selector, - assert_name_match=True): - """Search the submodules `submodnames` of a module `this_modname` - for class objects for which `class_selector(class)` returns - `True`. These classes are instantiated, and the `instance.name` - is compared to the `submodname` (if `assert_name_match` is - `True`). - - The instances are further arranged into a dependency - :class:`hooke.util.graph.Graph` according to their - `instance.dependencies()` values. The topologically sorted graph - is returned. - """ - instances = {} - for submodname in submodnames: - count = len([s for s in submodnames if s == submodname]) - assert count > 0, 'No %s entries: %s' % (submodname, submodnames) - assert count == 1, 'Multiple (%d) %s entries: %s' \ - % (count, submodname, submodnames) - this_mod = __import__(this_modname, fromlist=[submodname]) - submod = getattr(this_mod, submodname) - for objname in dir(submod): - obj = getattr(submod, objname) - if class_selector(obj): - instance = obj() - if assert_name_match == True and instance.name != submodname: - raise Exception( - 'Instance name %s does not match module name %s' - % (instance.name, submodname)) - instances[instance.name] = instance - graph = Graph([Node([instances[name] for name in i.dependencies()], - data=i) - for i in instances.values()]) - graph.topological_sort() - return graph - -class IsSubclass (object): - """A safe subclass comparator. - - Examples - -------- - - >>> class A (object): - ... pass - >>> class B (A): - ... pass - >>> C = 5 - >>> is_subclass = IsSubclass(A) - >>> is_subclass(A) - True - >>> is_subclass = IsSubclass(A, blacklist=[A]) - >>> is_subclass(A) - False - >>> is_subclass(B) - True - >>> is_subclass(C) - False +# 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. """ - def __init__(self, base_class, blacklist=None): - self.base_class = base_class - if blacklist == None: - blacklist = [] - self.blacklist = blacklist - def __call__(self, other): - try: - subclass = issubclass(other, self.base_class) - except TypeError: - return False - if other in self.blacklist: - return False - return subclass + 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__, @@ -193,6 +169,7 @@ def default_settings(): return settings def load_graph(graph, config, include_section): + enabled = {} items = [] for node in graph: item = node.data @@ -200,10 +177,15 @@ def load_graph(graph, config, include_section): 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: - try: - item.config = dict(config.items(item.setting_section)) - except configparser.NoSectionError: - pass + 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