Ran update_copyright.py, updating all the copyright blurbs and adding AUTHORS.
[hooke.git] / hooke / plugin / __init__.py
index 0770f9e6f815fbaa81ea361fb6f1eb5a569b7993..075cdffa0e063c7437a09a666e2f96967e6327dc 100644 (file)
-#!/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
 
 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),
+#    ('curvetools', 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),
-    ('tutorial', True),
-    ('viewer', 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),
+#    ('peakspot', True),
+#    ('procplots', True),
+#    ('review', True),
+#    ('showconvoluted', True),
+#    ('superimpose', True),
+#    ('tutorial', True),
     ]
 """List of plugin modules and whether they should be included by
-default.
+default.  TODO: autodiscovery
+"""
+
+BUILTIN_MODULES = [
+    'config',
+    'debug',
+    'note',
+    'playlist',
+    'system',
+    ]
+"""List of builtin modules.  TODO: autodiscovery
 """
 
-def Plugin (object):
-    """The pluggable collection of Hooke commands.
+PLUGIN_SETTING_SECTION = 'plugins'
+"""Name of the config section which controls plugin selection.
+"""
+
+
+# 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.
     """
-    name = "base plugin"
+    def __init__(self, name):
+        self.name = name
+        self.setting_section = '%s plugin' % self.name
+        self.config = {}
 
     def dependencies(self):
-        """Return a list of Plugins we require."""
+        """Return a list 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 a list of :class:`hooke.command.Command`\s provided.
+        """
         return []
 
-PLUGINS = {}
-"""(name,instance) :class:`dict` of all possible :class:`Plugin`\s.
+class Builtin (Plugin):
+    """A required collection of Hooke commands.
+
+    These "core" plugins provide essential administrative commands
+    (playlist handling, etc.).
+    """
+    pass
+
+# 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):
+    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)
+        if include == True:
+            try:
+                item.config = dict(config.items(item.setting_section))
+            except configparser.NoSectionError:
+                pass
+            items.append(item)
+    return items