Close parenthesis on column append in DifferenceCommand
[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 modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 #
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
13 # 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 import logging
27
28 from ..config import Setting
29 from ..util.pluggable import IsSubclass, construct_graph
30
31
32 PLUGIN_MODULES = [
33 #    ('autopeak', True),
34     ('convfilt', True),
35     ('cut', True),
36 #    ('fclamp', True),
37 #    ('flatfilts-rolf', True),
38     ('flatfilt', True),
39 #    ('jumpstat', True),
40 #    ('massanalysis', True),
41 #    ('multidistance', True),
42 #    ('multifit', True),
43 #    ('pcluster', True),
44     ('polymer_fit', 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     'license',
62     'note',
63     'playlist',
64     'playlists',
65     'system',
66     ]
67 """List of builtin modules.  TODO: autodiscovery
68 """
69
70 PLUGIN_SETTING_SECTION = 'plugins'
71 """Name of the config section which controls plugin selection.
72 """
73
74
75 # Plugins and settings
76
77 class Plugin (object):
78     """A pluggable collection of Hooke commands.
79
80     Fulfills the same role for Hooke that a software package does for
81     an operating system: contains a chunk of related commands and
82     routines.  Command configuration also happens on the `Plugin`
83     level, with per-plugin sections in the configuration file.
84     """
85     def __init__(self, name):
86         self.name = name
87         self.setting_section = '%s plugin' % self.name
88         self.config = {}
89         self._commands = []
90
91     def dependencies(self):
92         """Return a list of names of :class:`Plugin`\s we require."""
93         return []
94
95     def default_settings(self):
96         """Return a list of :class:`hooke.config.Setting`\s for any
97         configurable plugin settings.
98
99         The suggested section setting is::
100
101             Setting(section=self.setting_section, help=self.__doc__)
102         """
103         return []
104
105     def commands(self):
106         """Return a list of :class:`hooke.command.Command`\s provided.
107         """
108         return list(self._commands)
109
110
111 class Builtin (Plugin):
112     """A required collection of Hooke commands.
113
114     These "core" plugins provide essential administrative commands
115     (playlist handling, etc.).
116     """
117     pass
118
119
120 # Plugin utility functions
121
122 def argument_to_setting(section_name, argument):
123     """Convert an :class:`~hooke.command.Argument` to a
124     `~hooke.conf.Setting`.
125
126     This is useful if, for example, you want to define arguments with
127     configurable default values.
128
129     Conversion is lossy transition, because
130     :class:`~hooke.command.Argument`\s store more information than
131     `~hooke.conf.Setting`\s.
132     """
133     return Setting(section_name, option=argument.name, value=argument.default,
134                    type=argument.type, count=argument.count,
135                    help=argument._help)
136
137
138 # Construct plugin dependency graph and load plugin instances.
139
140 PLUGIN_GRAPH = construct_graph(
141     this_modname=__name__,
142     submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES,
143     class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin]))
144 """Topologically sorted list of all possible :class:`Plugin`\s and
145 :class:`Builtin`\s.
146 """
147
148 def default_settings():
149     settings = [Setting(PLUGIN_SETTING_SECTION,
150                         help='Enable/disable default plugins.')]
151     for pnode in PLUGIN_GRAPH:
152         if pnode.data.name in BUILTIN_MODULES:
153             continue # builtin inclusion is not optional
154         plugin = pnode.data
155         default_include = [di for mod_name,di in PLUGIN_MODULES
156                            if mod_name == plugin.name][0]
157         help = 'Commands: ' + ', '.join([c.name for c in plugin.commands()])
158         settings.append(Setting(
159                 section=PLUGIN_SETTING_SECTION,
160                 option=plugin.name,
161                 value=str(default_include),
162                 help=help,
163                 ))
164     for pnode in PLUGIN_GRAPH:
165         plugin = pnode.data
166         settings.extend(plugin.default_settings())
167     return settings
168
169 def load_graph(graph, config, include_section):
170     enabled = {}
171     items = []
172     conditions = config.items('conditions')
173     for node in graph:
174         item = node.data
175         try:
176             include = config.getboolean(include_section, item.name)
177         except configparser.NoOptionError:
178             include = True # non-optional include (e.g. a Builtin)
179         enabled[item.name] = include
180         if include == True:
181             for dependency in node:
182                 if enabled.get(dependency.data.name, None) != True:
183                     log = logging.getLogger('hooke')
184                     log.warn(
185                      'could not setup plugin %s.  unsatisfied dependency on %s.'
186                      % (item.name, dependency.data.name))
187                     enabled[item.name] = False
188                     continue
189             try:
190                 item.config = dict(config.items(item.setting_section))
191             except configparser.NoSectionError:
192                 item.config = {}
193             for key,value in conditions:
194                 if key not in item.config:
195                     item.config[key] = value
196             items.append(item)
197     return items