1 """The `plugin` module provides optional submodules that add new Hooke
4 All of the science happens in here.
7 import ConfigParser as configparser
9 from ..config import Setting
10 from ..util.graph import Node, Graph
15 # ('curvetools', True),
18 # ('flatfilts-rolf', True),
19 # ('flatfilts', True),
20 # ('generalclamp', True),
21 # ('generaltccd', True),
22 # ('generalvclamp', True),
25 # ('massanalysis', True),
26 # ('multidistance', True),
30 # ('procplots', True),
32 # ('showconvoluted', True),
33 # ('superimpose', True),
36 """List of plugin modules and whether they should be included by
37 default. TODO: autodiscovery
46 """List of builtin modules. TODO: autodiscovery
49 PLUGIN_SETTING_SECTION = 'plugins'
50 """Name of the config section which controls plugin selection.
54 # Plugins and settings
56 class Plugin (object):
57 """A pluggable collection of Hooke commands.
59 Fulfills the same role for Hooke that a software package does for
62 def __init__(self, name):
64 self.setting_section = '%s plugin' % self.name
67 def dependencies(self):
68 """Return a list of :class:`Plugin`\s we require."""
71 def default_settings(self):
72 """Return a list of :class:`hooke.config.Setting`\s for any
73 configurable plugin settings.
75 The suggested section setting is::
77 Setting(section=self.setting_section, help=self.__doc__)
82 """Return a list of :class:`hooke.command.Command`\s provided.
86 class Builtin (Plugin):
87 """A required collection of Hooke commands.
89 These "core" plugins provide essential administrative commands
90 (playlist handling, etc.).
94 # Construct plugin dependency graph and load plugin instances.
96 def construct_graph(this_modname, submodnames, class_selector,
97 assert_name_match=True):
98 """Search the submodules `submodnames` of a module `this_modname`
99 for class objects for which `class_selector(class)` returns
100 `True`. These classes are instantiated, and the `instance.name`
101 is compared to the `submodname` (if `assert_name_match` is
104 The instances are further arranged into a dependency
105 :class:`hooke.util.graph.Graph` according to their
106 `instance.dependencies()` values. The topologically sorted graph
110 for submodname in submodnames:
111 count = len([s for s in submodnames if s == submodname])
112 assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
113 assert count == 1, 'Multiple (%d) %s entries: %s' \
114 % (count, submodname, submodnames)
115 this_mod = __import__(this_modname, fromlist=[submodname])
116 submod = getattr(this_mod, submodname)
117 for objname in dir(submod):
118 obj = getattr(submod, objname)
119 if class_selector(obj):
121 if assert_name_match == True and instance.name != submodname:
123 'Instance name %s does not match module name %s'
124 % (instance.name, submodname))
125 instances[instance.name] = instance
127 for i in instances.values(): # make nodes for each instance
128 nodes[i.name] = Node(data=i)
129 for n in nodes.values(): # fill in dependencies for each node
130 n.extend([nodes[name] for name in n.data.dependencies()])
131 graph = Graph(nodes.values())
132 graph.topological_sort()
135 class IsSubclass (object):
136 """A safe subclass comparator.
141 >>> class A (object):
146 >>> is_subclass = IsSubclass(A)
149 >>> is_subclass = IsSubclass(A, blacklist=[A])
157 def __init__(self, base_class, blacklist=None):
158 self.base_class = base_class
159 if blacklist == None:
161 self.blacklist = blacklist
162 def __call__(self, other):
164 subclass = issubclass(other, self.base_class)
167 if other in self.blacklist:
171 PLUGIN_GRAPH = construct_graph(
172 this_modname=__name__,
173 submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES,
174 class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin]))
175 """Topologically sorted list of all possible :class:`Plugin`\s and
179 def default_settings():
180 settings = [Setting(PLUGIN_SETTING_SECTION,
181 help='Enable/disable default plugins.')]
182 for pnode in PLUGIN_GRAPH:
183 if pnode.data.name in BUILTIN_MODULES:
184 continue # builtin inclusion is not optional
186 default_include = [di for mod_name,di in PLUGIN_MODULES
187 if mod_name == plugin.name][0]
188 help = 'Commands: ' + ', '.join([c.name for c in plugin.commands()])
189 settings.append(Setting(
190 section=PLUGIN_SETTING_SECTION,
192 value=str(default_include),
195 for pnode in PLUGIN_GRAPH:
197 settings.extend(plugin.default_settings())
200 def load_graph(graph, config, include_section):
205 include = config.getboolean(include_section, item.name)
206 except configparser.NoOptionError:
207 include = True # non-optional include (e.g. a Builtin)
210 item.config = dict(config.items(item.setting_section))
211 except configparser.NoSectionError: