All distros bump to 1.0.7-6
[ejpi] / ejpi / plugin_utils.py
1 #!/usr/bin/env python
2
3
4 from __future__ import with_statement
5
6
7 import sys
8 import os
9 import inspect
10 import ConfigParser
11
12 from util import qtpieboard
13 from util import io
14 import operation
15
16
17 class CommandStackHandler(object):
18
19         def __init__(self, stack, command, operator):
20                 self.command = command
21
22                 self.__stack = stack
23                 self.__operator = operator
24
25         def handler(self, commandName, activeModifiers):
26                 self.__stack.apply_operation(self.__operator)
27
28
29 class PieKeyboardPlugin(object):
30
31         def __init__(self, name, factory):
32                 self.name = name
33                 self.factory = factory
34                 self.__handler = None
35
36         def setup(self, calcStack, boardHandler):
37                 self.__handler = boardHandler
38
39                 boardTree = self.factory.map
40
41                 keyboardName = boardTree["name"]
42                 keyTree = boardTree["keys"]
43
44                 keyboard = qtpieboard.PieKeyboard()
45                 qtpieboard.load_keyboard(keyboardName, keyTree, keyboard, self.__handler, self.factory.iconPaths)
46
47                 for commandName, operator in self.factory.commands.iteritems():
48                         handler = CommandStackHandler(calcStack, commandName, operator)
49                         self.__handler.register_command_handler(commandName, handler.handler)
50
51                 return keyboard
52
53         def tear_down(self):
54                 for commandName, operator in self.factory.commands.itervalues():
55                         self.__handler.unregister_command_handler(commandName)
56
57                 # Leave our self completely unusable
58                 self.name = None
59                 self.factory = None
60                 self.__handler = None
61
62
63 class PieKeyboardPluginFactory(object):
64
65         def __init__(self, pluginName, icon, keyboardMap, iconPaths):
66                 self.name = pluginName
67                 self.map = keyboardMap
68                 self.commands = {}
69                 self.icon = icon
70                 self.iconPaths = iconPaths
71
72         def register_operation(self, commandName, operator):
73                 self.commands[commandName] = operator
74
75         def construct_keyboard(self):
76                 plugin = PieKeyboardPlugin(self.name, self)
77                 return plugin
78
79
80 class PluginManager(object):
81
82         def __init__(self, pluginType):
83                 self._pluginType = pluginType
84                 self._plugins = {}
85                 self._enabled = set()
86
87                 self.__searchPaths = []
88
89         def add_path(self, *paths):
90                 self.__searchPaths.append(paths)
91                 self.__scan(paths)
92
93         def rescan(self):
94                 self._plugins = {}
95                 self.__scan(self.__searchPaths)
96
97         def plugin_info(self, pluginId):
98                 pluginData = self._plugins[pluginId]
99                 return pluginData["name"], pluginData["version"], pluginData["description"]
100
101         def plugins(self):
102                 for id, pluginData in self._plugins.iteritems():
103                         yield id, pluginData["name"], pluginData["version"], pluginData["description"]
104
105         def enable_plugin(self, id):
106                 assert id in self._plugins, "Can't find plugin %s in the search path %r" % (id, self.__searchPaths)
107                 self._load_module(id)
108                 self._enabled.add(id)
109
110         def disable_plugin(self, id):
111                 self._enabled.remove(id)
112
113         def lookup_plugin(self, name):
114                 for id, data in self._plugins.iteritems():
115                         if data["name"] == name:
116                                 return id
117
118         def _load_module(self, id):
119                 pluginData = self._plugins[id]
120
121                 if "module" not in pluginData:
122                         pluginPath = pluginData["pluginpath"]
123                         dataPath = pluginData["datapath"]
124                         assert dataPath.endswith(".ini")
125
126                         dataPath = io.relpath(pluginPath, dataPath)
127                         pythonPath = dataPath[0:-len(".ini")]
128                         modulePath = fspath_to_ipath(pythonPath, "")
129
130                         sys.path.append(pluginPath)
131                         try:
132                                 module = __import__(modulePath)
133                         finally:
134                                 sys.path.remove(pluginPath)
135                         pluginData["module"] = module
136                 else:
137                         # @todo Decide if want to call reload
138                         module = pluginData["module"]
139
140                 return module
141
142         def __scan(self, paths):
143                 pluginDataFiles = find_plugins(paths, ".ini")
144
145                 for pluginPath, pluginDataFile in pluginDataFiles:
146                         config = ConfigParser.SafeConfigParser()
147                         config.read(pluginDataFile)
148
149                         name = config.get(self._pluginType, "name")
150                         version = config.get(self._pluginType, "version")
151                         description = config.get(self._pluginType, "description")
152
153                         self._plugins[pluginDataFile] = {
154                                 "name": name,
155                                 "version": version,
156                                 "description": description,
157                                 "datapath": pluginDataFile,
158                                 "pluginpath": pluginPath,
159                         }
160
161
162 class ConstantPluginManager(PluginManager):
163
164         def __init__(self):
165                 super(ConstantPluginManager, self).__init__("Constants")
166                 self.__constants = {}
167                 self.__constantsCache = {}
168                 self.__isCacheDirty = False
169
170         def enable_plugin(self, id):
171                 super(ConstantPluginManager, self).enable_plugin(id)
172                 self.__constants[id] = dict(
173                         extract_instance_from_plugin(self._plugins[id]["module"], operation.Operation)
174                 )
175                 self.__isCacheDirty = True
176
177         def disable_plugin(self, id):
178                 super(ConstantPluginManager, self).disable_plugin(id)
179                 self.__isCacheDirty = True
180
181         @property
182         def constants(self):
183                 if self.__isCacheDirty:
184                         self.__update_cache()
185                 return self.__constantsCache
186
187         def __update_cache(self):
188                 self.__constantsCache.clear()
189                 for pluginId in self._enabled:
190                         self.__constantsCache.update(self.__constants[pluginId])
191                 self.__isCacheDirty = False
192
193
194 class OperatorPluginManager(PluginManager):
195
196         def __init__(self):
197                 super(OperatorPluginManager, self).__init__("Operator")
198                 self.__operators = {}
199                 self.__operatorsCache = {}
200                 self.__isCacheDirty = False
201
202         def enable_plugin(self, id):
203                 super(OperatorPluginManager, self).enable_plugin(id)
204                 operators = (
205                         extract_class_from_plugin(self._plugins[id]["module"], operation.Operation)
206                 )
207                 self.__operators[id] = dict(
208                         (op.symbol, op)
209                         for op in operators
210                 )
211                 self.__isCacheDirty = True
212
213         def disable_plugin(self, id):
214                 super(OperatorPluginManager, self).disable_plugin(id)
215                 self.__isCacheDirty = True
216
217         @property
218         def operators(self):
219                 if self.__isCacheDirty:
220                         self.__update_cache()
221                 return self.__operatorsCache
222
223         def __update_cache(self):
224                 self.__operatorsCache.clear()
225                 for pluginId in self._enabled:
226                         self.__operatorsCache.update(self.__operators[pluginId])
227                 self.__isCacheDirty = False
228
229
230 class KeyboardPluginManager(PluginManager):
231
232         def __init__(self):
233                 super(KeyboardPluginManager, self).__init__("Keyboard")
234                 self.__keyboards = {}
235                 self.__keyboardsCache = {}
236                 self.__isCacheDirty = False
237
238         def enable_plugin(self, id):
239                 super(KeyboardPluginManager, self).enable_plugin(id)
240                 keyboards = (
241                         extract_instance_from_plugin(self._plugins[id]["module"], PieKeyboardPluginFactory)
242                 )
243                 self.__keyboards[id] = dict(
244                         (board.name, board)
245                         for boardVariableName, board in keyboards
246                 )
247                 self.__isCacheDirty = True
248
249         def disable_plugin(self, id):
250                 super(KeyboardPluginManager, self).disable_plugin(id)
251                 self.__isCacheDirty = True
252
253         @property
254         def keyboards(self):
255                 if self.__isCacheDirty:
256                         self.__update_cache()
257                 return self.__keyboardsCache
258
259         def __update_cache(self):
260                 self.__keyboardsCache.clear()
261                 for pluginId in self._enabled:
262                         self.__keyboardsCache.update(self.__keyboards[pluginId])
263                 self.__isCacheDirty = False
264
265
266 def fspath_to_ipath(fsPath, extension = ".py"):
267         """
268         >>> fspath_to_ipath("user/test/file.py")
269         'user.test.file'
270         """
271         assert fsPath.endswith(extension)
272         CURRENT_DIR = "."+os.sep
273         CURRENT_DIR_LEN = len(CURRENT_DIR)
274         if fsPath.startswith(CURRENT_DIR):
275                 fsPath = fsPath[CURRENT_DIR_LEN:]
276
277         if extension:
278                 fsPath = fsPath[0:-len(extension)]
279         parts = fsPath.split(os.sep)
280         return ".".join(parts)
281
282
283 def find_plugins(searchPaths, fileType=".py"):
284         pythonFiles = (
285                 (path, os.path.join(root, file))
286                 for path in searchPaths
287                 for root, dirs, files in os.walk(path)
288                 for file in files
289                         if file.endswith(fileType)
290         )
291         return pythonFiles
292
293
294 def extract_class_from_plugin(pluginModule, cls):
295         try:
296                 for item in pluginModule.__dict__.itervalues():
297                         try:
298                                 if cls in inspect.getmro(item):
299                                         yield item
300                         except AttributeError:
301                                 pass
302         except AttributeError:
303                 pass
304
305
306 def extract_instance_from_plugin(pluginModule, cls):
307         try:
308                 for name, item in pluginModule.__dict__.iteritems():
309                         try:
310                                 if isinstance(item, cls):
311                                         yield name, item
312                         except AttributeError:
313                                 pass
314         except AttributeError:
315                 pass