65835a7b5d0208be3e16a6d67f98f948a103a351
[hooke.git] / hooke / plugin / __init__.py
1 # Copyright
2
3 """The `plugin` module provides optional submodules that add new Hooke
4 commands.
5
6 All of the science happens in here.
7 """
8
9 import ConfigParser as configparser
10
11 from ..config import Setting
12 from ..util.pluggable import IsSubclass, construct_graph
13
14
15 PLUGIN_MODULES = [
16 #    ('autopeak', True),
17 #    ('curvetools', True),
18     ('cut', True),
19 #    ('fit', True),
20 #    ('flatfilts-rolf', True),
21 #    ('flatfilts', True),
22 #    ('generalclamp', True),
23 #    ('generaltccd', True),
24 #    ('generalvclamp', True),
25 #    ('jumpstat', True),
26 #    ('macro', True),
27 #    ('massanalysis', True),
28 #    ('multidistance', True),
29 #    ('multifit', True),
30 #    ('pcluster', True),
31 #    ('peakspot', True),
32 #    ('procplots', True),
33 #    ('review', True),
34 #    ('showconvoluted', True),
35 #    ('superimpose', True),
36 #    ('tutorial', True),
37     ]
38 """List of plugin modules and whether they should be included by
39 default.  TODO: autodiscovery
40 """
41
42 BUILTIN_MODULES = [
43     'config',
44     'debug',
45     'note',
46     'playlist',
47     'system',
48     ]
49 """List of builtin modules.  TODO: autodiscovery
50 """
51
52 PLUGIN_SETTING_SECTION = 'plugins'
53 """Name of the config section which controls plugin selection.
54 """
55
56
57 # Plugins and settings
58
59 class Plugin (object):
60     """A pluggable collection of Hooke commands.
61
62     Fulfills the same role for Hooke that a software package does for
63     an operating system.
64     """
65     def __init__(self, name):
66         self.name = name
67         self.setting_section = '%s plugin' % self.name
68         self.config = {}
69
70     def dependencies(self):
71         """Return a list of :class:`Plugin`\s we require."""
72         return []
73
74     def default_settings(self):
75         """Return a list of :class:`hooke.config.Setting`\s for any
76         configurable plugin settings.
77
78         The suggested section setting is::
79
80             Setting(section=self.setting_section, help=self.__doc__)
81         """
82         return []
83
84     def commands(self):
85         """Return a list of :class:`hooke.command.Command`\s provided.
86         """
87         return []
88
89 class Builtin (Plugin):
90     """A required collection of Hooke commands.
91
92     These "core" plugins provide essential administrative commands
93     (playlist handling, etc.).
94     """
95     pass
96
97 # Construct plugin dependency graph and load plugin instances.
98
99 PLUGIN_GRAPH = construct_graph(
100     this_modname=__name__,
101     submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES,
102     class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin]))
103 """Topologically sorted list of all possible :class:`Plugin`\s and
104 :class:`Builtin`\s.
105 """
106
107 def default_settings():
108     settings = [Setting(PLUGIN_SETTING_SECTION,
109                         help='Enable/disable default plugins.')]
110     for pnode in PLUGIN_GRAPH:
111         if pnode.data.name in BUILTIN_MODULES:
112             continue # builtin inclusion is not optional
113         plugin = pnode.data
114         default_include = [di for mod_name,di in PLUGIN_MODULES
115                            if mod_name == plugin.name][0]
116         help = 'Commands: ' + ', '.join([c.name for c in plugin.commands()])
117         settings.append(Setting(
118                 section=PLUGIN_SETTING_SECTION,
119                 option=plugin.name,
120                 value=str(default_include),
121                 help=help,
122                 ))
123     for pnode in PLUGIN_GRAPH:
124         plugin = pnode.data
125         settings.extend(plugin.default_settings())
126     return settings
127
128 def load_graph(graph, config, include_section):
129     items = []
130     for node in graph:
131         item = node.data
132         try:
133             include = config.getboolean(include_section, item.name)
134         except configparser.NoOptionError:
135             include = True # non-optional include (e.g. a Builtin)
136         if include == True:
137             try:
138                 item.config = dict(config.items(item.setting_section))
139             except configparser.NoSectionError:
140                 pass
141             items.append(item)
142     return items