Major plugin restructuring.
[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     ('convfilt', True),
34     ('cut', True),
35 #    ('fclamp', True),
36 #    ('fit', True),
37 #    ('flatfilts-rolf', True),
38     ('flatfilt', True),
39 #    ('jumpstat', True),
40 #    ('macro', True),
41 #    ('massanalysis', True),
42 #    ('multidistance', True),
43 #    ('multifit', True),
44 #    ('pcluster', True),
45 #    ('procplots', True),
46 #    ('review', True),
47 #    ('showconvoluted', True),
48 #    ('superimpose', True),
49 #    ('tccd', True),
50 #    ('tutorial', True),
51     ('vclamp', True),
52     ]
53 """List of plugin modules and whether they should be included by
54 default.  TODO: autodiscovery
55 """
56
57 BUILTIN_MODULES = [
58     'config',
59     'curve',
60     'debug',
61     'note',
62     'playlist',
63     'system',
64     ]
65 """List of builtin modules.  TODO: autodiscovery
66 """
67
68 PLUGIN_SETTING_SECTION = 'plugins'
69 """Name of the config section which controls plugin selection.
70 """
71
72
73 # Plugins and settings
74
75 class Plugin (object):
76     """A pluggable collection of Hooke commands.
77
78     Fulfills the same role for Hooke that a software package does for
79     an operating system.
80     """
81     def __init__(self, name):
82         self.name = name
83         self.setting_section = '%s plugin' % self.name
84         self.config = {}
85         self._commands = []
86
87     def dependencies(self):
88         """Return a list of names of :class:`Plugin`\s we require."""
89         return []
90
91     def default_settings(self):
92         """Return a list of :class:`hooke.config.Setting`\s for any
93         configurable plugin settings.
94
95         The suggested section setting is::
96
97             Setting(section=self.setting_section, help=self.__doc__)
98         """
99         return []
100
101     def commands(self):
102         """Return a list of :class:`hooke.command.Command`\s provided.
103         """
104         return list(self._commands)
105
106 class Builtin (Plugin):
107     """A required collection of Hooke commands.
108
109     These "core" plugins provide essential administrative commands
110     (playlist handling, etc.).
111     """
112     pass
113
114 # Plugin utility functions
115
116 def argument_to_setting(section_name, argument):
117     """Convert an :class:`~hooke.command.Argument` to a
118     `~hooke.conf.Setting`.
119
120     This is a lossy transition, because
121     :class:`~hooke.command.Argument`\s store more information than
122     `~hooke.conf.Setting`\s.
123     """
124     return Setting(section_name, option=argument.name, value=argument.default,
125                    help=argument._help)
126
127 # Construct plugin dependency graph and load plugin instances.
128
129 PLUGIN_GRAPH = construct_graph(
130     this_modname=__name__,
131     submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES,
132     class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin]))
133 """Topologically sorted list of all possible :class:`Plugin`\s and
134 :class:`Builtin`\s.
135 """
136
137 def default_settings():
138     settings = [Setting(PLUGIN_SETTING_SECTION,
139                         help='Enable/disable default plugins.')]
140     for pnode in PLUGIN_GRAPH:
141         if pnode.data.name in BUILTIN_MODULES:
142             continue # builtin inclusion is not optional
143         plugin = pnode.data
144         default_include = [di for mod_name,di in PLUGIN_MODULES
145                            if mod_name == plugin.name][0]
146         help = 'Commands: ' + ', '.join([c.name for c in plugin.commands()])
147         settings.append(Setting(
148                 section=PLUGIN_SETTING_SECTION,
149                 option=plugin.name,
150                 value=str(default_include),
151                 help=help,
152                 ))
153     for pnode in PLUGIN_GRAPH:
154         plugin = pnode.data
155         settings.extend(plugin.default_settings())
156     return settings
157
158 def load_graph(graph, config, include_section):
159     items = []
160     for node in graph:
161         item = node.data
162         try:
163             include = config.getboolean(include_section, item.name)
164         except configparser.NoOptionError:
165             include = True # non-optional include (e.g. a Builtin)
166         if include == True:
167             try:
168                 item.config = dict(config.items(item.setting_section))
169             except configparser.NoSectionError:
170                 pass
171             items.append(item)
172     return items