Rework Plugin.commands() to include _setup_commands().
[hooke.git] / hooke / plugin / __init__.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
5 # Hooke is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation, either
8 # version 3 of the License, or (at your option) any later version.
9 #
10 # Hooke is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke.  If not, see
17 # <http://www.gnu.org/licenses/>.
18
19 """The `plugin` module provides optional submodules that add new Hooke
20 commands.
21
22 All of the science happens in here.
23 """
24
25 import ConfigParser as configparser
26
27 from ..config import Setting
28 from ..util.pluggable import IsSubclass, construct_graph
29
30
31 PLUGIN_MODULES = [
32 #    ('autopeak', True),
33 #    ('curvetools', True),
34     ('cut', True),
35 #    ('fit', True),
36 #    ('flatfilts-rolf', True),
37     ('flatfilt', True),
38 #    ('generalclamp', True),
39 #    ('generaltccd', True),
40 #    ('generalvclamp', True),
41 #    ('jumpstat', True),
42 #    ('macro', True),
43 #    ('massanalysis', True),
44 #    ('multidistance', True),
45 #    ('multifit', True),
46 #    ('pcluster', True),
47 #    ('peakspot', True),
48 #    ('procplots', True),
49 #    ('review', True),
50 #    ('showconvoluted', True),
51 #    ('superimpose', True),
52 #    ('tutorial', True),
53     ]
54 """List of plugin modules and whether they should be included by
55 default.  TODO: autodiscovery
56 """
57
58 BUILTIN_MODULES = [
59     'config',
60     'curve',
61     'debug',
62     'note',
63     'playlist',
64     'system',
65     ]
66 """List of builtin modules.  TODO: autodiscovery
67 """
68
69 PLUGIN_SETTING_SECTION = 'plugins'
70 """Name of the config section which controls plugin selection.
71 """
72
73
74 # Plugins and settings
75
76 class Plugin (object):
77     """A pluggable collection of Hooke commands.
78
79     Fulfills the same role for Hooke that a software package does for
80     an operating system.
81     """
82     def __init__(self, name):
83         self.name = name
84         self.setting_section = '%s plugin' % self.name
85         self.config = {}
86         self._commands = []
87
88     def dependencies(self):
89         """Return a list of names of :class:`Plugin`\s we require."""
90         return []
91
92     def default_settings(self):
93         """Return a list of :class:`hooke.config.Setting`\s for any
94         configurable plugin settings.
95
96         The suggested section setting is::
97
98             Setting(section=self.setting_section, help=self.__doc__)
99         """
100         return []
101
102     def _setup_commands(self):
103         """Perform internal setup on stored commands.
104
105         Currently:
106
107         * Adds a `plugin` attribute to each command so they can access
108           the plugin configuration with `.plugin.config`.
109         """
110         for command in self._commands:
111             command.plugin = self
112
113     def commands(self):
114         """Return a list of :class:`hooke.command.Command`\s provided.
115         """
116         return list(self._commands)
117
118 class Builtin (Plugin):
119     """A required collection of Hooke commands.
120
121     These "core" plugins provide essential administrative commands
122     (playlist handling, etc.).
123     """
124     pass
125
126 # Construct plugin dependency graph and load plugin instances.
127
128 PLUGIN_GRAPH = construct_graph(
129     this_modname=__name__,
130     submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES,
131     class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin]))
132 """Topologically sorted list of all possible :class:`Plugin`\s and
133 :class:`Builtin`\s.
134 """
135
136 def default_settings():
137     settings = [Setting(PLUGIN_SETTING_SECTION,
138                         help='Enable/disable default plugins.')]
139     for pnode in PLUGIN_GRAPH:
140         if pnode.data.name in BUILTIN_MODULES:
141             continue # builtin inclusion is not optional
142         plugin = pnode.data
143         default_include = [di for mod_name,di in PLUGIN_MODULES
144                            if mod_name == plugin.name][0]
145         help = 'Commands: ' + ', '.join([c.name for c in plugin.commands()])
146         settings.append(Setting(
147                 section=PLUGIN_SETTING_SECTION,
148                 option=plugin.name,
149                 value=str(default_include),
150                 help=help,
151                 ))
152     for pnode in PLUGIN_GRAPH:
153         plugin = pnode.data
154         settings.extend(plugin.default_settings())
155     return settings
156
157 def load_graph(graph, config, include_section):
158     items = []
159     for node in graph:
160         item = node.data
161         try:
162             include = config.getboolean(include_section, item.name)
163         except configparser.NoOptionError:
164             include = True # non-optional include (e.g. a Builtin)
165         if include == True:
166             try:
167                 item.config = dict(config.items(item.setting_section))
168             except configparser.NoSectionError:
169                 pass
170             items.append(item)
171     return items