3598017c92060f34247e78ad226183e7d5f8cd2f
[hooke.git] / hooke / plugin / __init__.py
1 # Copyright (C) 2010-2011 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     ('flatfilt', True),
38 #    ('jumpstat', True),
39 #    ('massanalysis', True),
40 #    ('multidistance', True),
41 #    ('multifit', True),
42 #    ('pcluster', True),
43     ('polymer_fit', True),
44 #    ('showconvoluted', True),
45 #    ('superimpose', True),
46 #    ('tccd', True),
47     ('tutorial', True),
48     ('vclamp', True),
49     ]
50 """List of plugin modules and whether they should be included by
51 default.  TODO: autodiscovery
52 """
53
54 BUILTIN_MODULES = [
55     'command_stack',
56     'config',
57     'curve',
58     'debug',
59     'engine',
60     'license',
61     'note',
62     'playlist',
63     'playlists',
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: contains a chunk of related commands and
81     routines.  Command configuration also happens on the `Plugin`
82     level, with per-plugin sections in the configuration file.
83     """
84     def __init__(self, name):
85         self.name = name
86         self.setting_section = '%s plugin' % self.name
87         self.config = {}
88         self._commands = []
89
90     def dependencies(self):
91         """Return a list of names of :class:`Plugin`\s we require."""
92         return []
93
94     def default_settings(self):
95         """Return a list of :class:`hooke.config.Setting`\s for any
96         configurable plugin settings.
97
98         The suggested section setting is::
99
100             Setting(section=self.setting_section, help=self.__doc__)
101         """
102         return []
103
104     def commands(self):
105         """Return a list of :class:`hooke.command.Command`\s provided.
106         """
107         return list(self._commands)
108
109
110 class Builtin (Plugin):
111     """A required collection of Hooke commands.
112
113     These "core" plugins provide essential administrative commands
114     (playlist handling, etc.).
115     """
116     pass
117
118
119 # Plugin utility functions
120
121 def argument_to_setting(section_name, argument):
122     """Convert an :class:`~hooke.command.Argument` to a
123     `~hooke.conf.Setting`.
124
125     This is useful if, for example, you want to define arguments with
126     configurable default values.
127
128     Conversion is lossy transition, because
129     :class:`~hooke.command.Argument`\s store more information than
130     `~hooke.conf.Setting`\s.
131     """
132     return Setting(section_name, option=argument.name, value=argument.default,
133                    type=argument.type, count=argument.count,
134                    help=argument._help)
135
136
137 # Construct plugin dependency graph and load plugin instances.
138
139 PLUGIN_GRAPH = construct_graph(
140     this_modname=__name__,
141     submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES,
142     class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin]))
143 """Topologically sorted list of all possible :class:`Plugin`\s and
144 :class:`Builtin`\s.
145 """
146
147 def default_settings():
148     settings = [Setting(PLUGIN_SETTING_SECTION,
149                         help='Enable/disable default plugins.')]
150     for pnode in PLUGIN_GRAPH:
151         if pnode.data.name in BUILTIN_MODULES:
152             continue # builtin inclusion is not optional
153         plugin = pnode.data
154         default_include = [di for mod_name,di in PLUGIN_MODULES
155                            if mod_name == plugin.name][0]
156         help = 'Commands: ' + ', '.join([c.name for c in plugin.commands()])
157         settings.append(Setting(
158                 section=PLUGIN_SETTING_SECTION,
159                 option=plugin.name,
160                 value=str(default_include),
161                 help=help,
162                 ))
163     for pnode in PLUGIN_GRAPH:
164         plugin = pnode.data
165         settings.extend(plugin.default_settings())
166     return settings
167
168 def load_graph(graph, config, include_section):
169     enabled = {}
170     items = []
171     for node in graph:
172         item = node.data
173         try:
174             include = config.getboolean(include_section, item.name)
175         except configparser.NoOptionError:
176             include = True # non-optional include (e.g. a Builtin)
177         enabled[item.name] = include
178         if include == True:
179             for dependency in node:
180                 if enabled.get(dependency.data.name, None) != True:
181                     log = logging.getLogger('hooke')
182                     log.warn(
183                      'could not setup plugin %s.  unsatisfied dependency on %s.'
184                      % (item.name, dependency.data.name))
185                     enabled[item.name] = False
186                     continue
187             items.append(item)
188     return items