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