-#!/usr/bin/env python
-"""The plugin module provides optional submodules that add new Hooke
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+"""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.
"""
+ 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 []
- def commands(self):
- """Return a list of Commands provided."""
+ """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 []
-PLUGINS = {}
-"""(name,instance) :class:`dict` of all possible :class:`Plugin`\s.
+ def commands(self):
+ """Return a list of :class:`hooke.command.Command`\s provided.
+ """
+ return list(self._commands)
+
+
+class Builtin (Plugin):
+ """A required collection of Hooke commands.
+
+ 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()
-
-#class Commands(wx.Panel):
-#
-# def __init__(self, parent):
-# # Use the WANTS_CHARS style so the panel doesn't eat the Return key.
-# wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200))
-#
-# self.CommandsTree = wx.TreeCtrl(self, -1, wx.Point(0, 0), wx.Size(160, 250), wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT)
-# imglist = wx.ImageList(16, 16, True, 2)
-# imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
-# imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))
-# self.CommandsTree.AssignImageList(imglist)
-# self.CommandsTree.AddRoot('Commands and Settings', 0)
-#
-# self.ExecuteButton = wx.Button(self, -1, 'Execute')
-#
-# sizer = wx.BoxSizer(wx.VERTICAL)
-# sizer.Add(self.CommandsTree, 1, wx.EXPAND)
-# sizer.Add(self.ExecuteButton, 0, wx.EXPAND)
-#
-# self.SetSizer(sizer)
-# sizer.Fit(self)
-#
-# def Initialize(self, plugins):
-# tree_root = self.CommandsTree.GetRootItem()
-# for plugin in plugins:
-# filename = ''.join([plugin, '.ini'])
-# path = lh.get_file_path(filename, ['plugins'])
-# config = ConfigObj()
-# if os.path.isfile(path):
-# config.filename = path
-# config.reload()
-# #append the ini file to the plugin
-# plugin_root = self.CommandsTree.AppendItem(tree_root, plugin, 0, data=wx.TreeItemData(config))
-# else:
-# plugin_root = self.CommandsTree.AppendItem(tree_root, plugin, 0)
-#
-# #add all commands to the tree
-# for command in plugins[plugin]:
-# command_label = command.replace('do_', '')
-# #do not add the ini file to the command (we'll access the ini file of the plugin (ie parent) instead, see above)
-# self.CommandsTree.AppendItem(plugin_root, command_label, 1)
-# self.CommandsTree.Expand(plugin_root)
-
-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