rUtilAnts 0.1.0.20091014

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,480 @@
1
+ #--
2
+ # Copyright (c) 2009 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module RUtilAnts
7
+
8
+ # Module that defines a generic way to handle plugins:
9
+ # * No pre-load: plugins files are required when needed only
10
+ # * Description files support: plugins files can give a description file, enumerating their dependencies, description...
11
+ # * Support for plugins categories
12
+ # * [If RDI is present]: Try to install dependencies before loading plugin instances
13
+ #
14
+ # Here are the following symbols that can be used in plugins' descriptions and are already interpreted by rUtilAnts:
15
+ # * *Dependencies* (<em>list<RDI::Model::DependencyDescription></em>): List of dependencies this plugin depends on
16
+ # * *PluginsDependencies* (<em>list[String,String]</em>): List of other plugins ([Category,Plugin]) this plugin depends on
17
+ # * *Enabled* (_Boolean_): Can this plugin be loaded ?
18
+ # Here are the symbols that are reserved bu rUtilAnts:
19
+ # * *PluginInstance* (_Object_): The real plugin instance
20
+ # * *PluginFileName* (_String_): The plugin's file name (or nil if none)
21
+ # * *PluginClassName* (_String_): Name of the plugin's class to instantiate
22
+ # * *PluginInitCode* (_Proc_): Code to call when instantiating the plugin (or nil if none)
23
+ # * *PluginIndex* (_Integer_): Unique incremental ID identifying the plugin in its category
24
+ # * *PluginName* (_String_): Name of the plugin
25
+ # * *PluginCategoryName* (_String_): Name of the category of the plugin
26
+ module Plugins
27
+
28
+ # Exception thrown when an unknown plugin is encountered
29
+ class UnknownPluginError < RuntimeError
30
+ end
31
+
32
+ # Exception thrown when an unknown category is encountered
33
+ class UnknownCategoryError < RuntimeError
34
+ end
35
+
36
+ # Exception thrown when a plugin is disabled
37
+ class DisabledPluginError < RuntimeError
38
+ end
39
+
40
+ # Exception thrown when a plugin can't load its dependencies
41
+ class PluginDependenciesError < RuntimeError
42
+ end
43
+
44
+ # Exception thrown when a plugin fails to instantiate
45
+ class FailedPluginError < RuntimeError
46
+ end
47
+
48
+ # Exception thrown when a plugin can't load its dependencies upon user request (ignore dependencies installation)
49
+ class PluginDependenciesIgnoredError < RuntimeError
50
+ end
51
+
52
+ # Exception thrown when a plugin can't load its dependencies, but the user is aware of it already
53
+ class PluginDependenciesUnresolvedError < RuntimeError
54
+ end
55
+
56
+ # Main class storing info about plugins
57
+ class PluginsManager
58
+
59
+ # Constructor
60
+ def initialize
61
+ # Map of plugins, per category
62
+ # map< String, map< String, map< Symbol, Object > > >
63
+ @Plugins = {}
64
+ end
65
+
66
+ # Register a new plugin
67
+ #
68
+ # Parameters:
69
+ # * *iCategoryName* (_String_): Category this plugin belongs to
70
+ # * *iPluginName* (_String_): Plugin name
71
+ # * *iFileName* (_String_): File name containing the plugin (can be nil)
72
+ # * *iDesc* (<em>map<Symbol,Object></em>): Plugin's description (can be nil)
73
+ # * *iClassName* (_String_): Name of the plugin class
74
+ # * *iInitCodeBlock* (_Proc_): Code block to call when initializing the real instance (can be nil)
75
+ def registerNewPlugin(iCategoryName, iPluginName, iFileName, iDesc, iClassName, iInitCodeBlock)
76
+ # Complete the description with some metadata
77
+ if (@Plugins[iCategoryName] == nil)
78
+ @Plugins[iCategoryName] = {}
79
+ end
80
+ lDesc = nil
81
+ if (iDesc == nil)
82
+ lDesc = {}
83
+ else
84
+ lDesc = iDesc.clone
85
+ end
86
+ lDesc[:PluginFileName] = iFileName
87
+ lDesc[:PluginInstance] = nil
88
+ lDesc[:PluginClassName] = iClassName
89
+ lDesc[:PluginInitCode] = iInitCodeBlock
90
+ lDesc[:PluginIndex] = @Plugins[iCategoryName].size
91
+ lDesc[:PluginName] = iPluginName
92
+ lDesc[:PluginCategoryName] = iCategoryName
93
+ @Plugins[iCategoryName][iPluginName] = lDesc
94
+ end
95
+
96
+ # Parse plugins from a given directory
97
+ #
98
+ # Parameters:
99
+ # * *iCategory* (_Object_): Category those plugins will belong to
100
+ # * *iDir* (_String_): Directory to parse for plugins
101
+ # * *iBaseClassNames* (_String_): The base class name of plugins to be instantiated
102
+ # * *iInitCodeBlock* (_CodeBlock_): Code to be executed first time the plugin will be instantiated (can be ommitted):
103
+ # ** *ioPlugin* (_Object_): Plugin instance
104
+ def parsePluginsFromDir(iCategory, iDir, iBaseClassNames, &iInitCodeBlock)
105
+ # Gather descriptions
106
+ # map< String, map >
107
+ lDescriptions = {}
108
+ lDescFiles = Dir.glob("#{iDir}/*.desc.rb")
109
+ lDescFiles.each do |iFileName|
110
+ lPluginName = File.basename(iFileName)[0..-9]
111
+ # Load the description file
112
+ begin
113
+ File.open(iFileName) do |iFile|
114
+ lDesc = eval(iFile.read, nil, iFileName)
115
+ if (lDesc.is_a?(Hash))
116
+ lDescriptions[lPluginName] = lDesc
117
+ else
118
+ logBug "Plugin description #{iFileName} is incorrect. The file should just describe a simple hash map."
119
+ end
120
+ end
121
+ rescue Exception
122
+ logExc $!, "Error while loading file #{iFileName}. Ignoring this description."
123
+ end
124
+ end
125
+ # Now, parse the plugins themselves
126
+ if (@Plugins[iCategory] == nil)
127
+ @Plugins[iCategory] = {}
128
+ end
129
+ (Dir.glob("#{iDir}/*.rb") - lDescFiles).each do |iFileName|
130
+ lPluginName = File.basename(iFileName)[0..-4]
131
+ # Don't load it now, but store it along with its description if it exists
132
+ if (@Plugins[iCategory][lPluginName] == nil)
133
+ # Check if we have a description
134
+ lDesc = lDescriptions[lPluginName]
135
+ registerNewPlugin(
136
+ iCategory,
137
+ lPluginName,
138
+ iFileName,
139
+ lDescriptions[lPluginName],
140
+ "#{iBaseClassNames}::#{lPluginName}",
141
+ iInitCodeBlock
142
+ )
143
+ else
144
+ logErr "Plugin named #{lPluginName} in category #{iCategory} already exists. Please name it differently. Ignoring it from #{iFileName}."
145
+ end
146
+ end
147
+ end
148
+
149
+ # Get the named plugin instance.
150
+ # Uses RDI if given in parameters or if Main RDI Installer defined to resolve Plugins' dependencies.
151
+ #
152
+ # Parameters:
153
+ # * *iCategory* (_Object_): Category those plugins will belong to
154
+ # * *iPluginName* (_String_): Plugin name
155
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
156
+ # ** *OnlyIfExtDepsResolved* (_Boolean_): Do we return the plugin only if there is no need to install external dependencies ? [optional = false]
157
+ # ** *RDIInstaller* (<em>RDI::Installer</em>): The RDI installer if available, or nil otherwise [optional = nil]
158
+ # ** *RDIContextModifiers* (<em>map<String,list<[String,Object]>></em>): The map of context modifiers to be filled by the RDI installer if specified, or nil if ignored [optional = nil]
159
+ # Return:
160
+ # * _Object_: The corresponding plugin, or nil in case of failure
161
+ # * _Exception_: The error, or nil in case of success
162
+ def getPluginInstance(iCategory, iPluginName, iParameters = {})
163
+ rPlugin = nil
164
+ rError = nil
165
+
166
+ lOnlyIfExtDepsResolved = iParameters[:OnlyIfExtDepsResolved]
167
+ if (lOnlyIfExtDepsResolved == nil)
168
+ lOnlyIfExtDepsResolved = false
169
+ end
170
+ lRDIInstaller = iParameters[:RDIInstaller]
171
+ lRDIContextModifiers = iParameters[:RDIContextModifiers]
172
+ if (@Plugins[iCategory] == nil)
173
+ rError = UnknownCategoryError.new("Unknown plugins category #{iCategory}.")
174
+ else
175
+ lDesc = @Plugins[iCategory][iPluginName]
176
+ if (lDesc == nil)
177
+ rError = UnknownPluginError.new("Unknown plugin #{iPluginName} in category #{iCategory}.")
178
+ elsif (lDesc[:Enabled] == false)
179
+ rError = DisabledPluginError.new("Plugin #{iPluginName} in category #{iCategory} is disabled.")
180
+ else
181
+ if (lDesc[:PluginInstance] == nil)
182
+ lSuccess = true
183
+ # If RDI is present, call it to get dependencies first if needed
184
+ if (lDesc[:Dependencies] != nil)
185
+ # If it is not given as parameter, try getting the singleton
186
+ if ((lRDIInstaller == nil) and
187
+ (defined?(RDI::Installer) != nil))
188
+ lRDIInstaller = RDI::Installer.getMainInstance
189
+ end
190
+ if (lRDIInstaller != nil)
191
+ if (lOnlyIfExtDepsResolved)
192
+ # Test that each dependency is accessible
193
+ lSuccess = true
194
+ lDesc[:Dependencies].each do |iDepDesc|
195
+ lSuccess = lRDIInstaller.testDependency(iDepDesc)
196
+ if (!lSuccess)
197
+ # It is useless to continue
198
+ break
199
+ end
200
+ end
201
+ else
202
+ # Load other dependencies
203
+ lError, lContextModifiers, lIgnored, lUnresolved = lRDIInstaller.ensureDependencies(lDesc[:Dependencies])
204
+ if (lRDIContextModifiers != nil)
205
+ lRDIContextModifiers.merge!(lContextModifiers)
206
+ end
207
+ lSuccess = ((lError == nil) and
208
+ (lIgnored.empty?) and
209
+ (lUnresolved.empty?))
210
+ if (!lSuccess)
211
+ if (!lIgnored.empty?)
212
+ rError = PluginDependenciesIgnoredError.new("Unable to load plugin #{iPluginName} without its dependencies (ignored #{lIgnored.size} dependencies).")
213
+ elsif (!lUnresolved.empty?)
214
+ rError = PluginDependenciesUnresolvedError.new("Unable to load plugin #{iPluginName} without its dependencies (couldn't load #{lUnresolved.size} dependencies):\n#{lError}")
215
+ else
216
+ rError = PluginDependenciesError.new("Could not load dependencies for plugin #{iPluginName}: #{lError}")
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ if (lSuccess)
223
+ if (lDesc[:PluginsDependencies] != nil)
224
+ # Load other plugins
225
+ lDesc[:PluginsDependencies].each do |iPluginInfo|
226
+ iPluginCategory, iPluginName = iPluginInfo
227
+ lPlugin, lError = getPluginInstance(iPluginCategory, iPluginName, iParameters)
228
+ lSuccess = (lError == nil)
229
+ if (!lSuccess)
230
+ # Don't try further
231
+ rError = PluginDependenciesError.new("Could not load plugins dependencies for plugin #{iPluginName}: #{lError}.")
232
+ break
233
+ end
234
+ end
235
+ end
236
+ if (lSuccess)
237
+ # Load the plugin
238
+ begin
239
+ # If the file name is to be required, do it now
240
+ if (lDesc[:PluginFileName] != nil)
241
+ require lDesc[:PluginFileName]
242
+ end
243
+ lPlugin = eval("#{lDesc[:PluginClassName]}.new")
244
+ # Add a reference to the description in the instantiated object
245
+ lPlugin.instance_variable_set(:@rUtilAnts_Desc, lDesc)
246
+ def lPlugin.pluginDescription
247
+ return @rUtilAnts_Desc
248
+ end
249
+ # Register this instance
250
+ lDesc[:PluginInstance] = lPlugin
251
+ # If needed, execute the init code
252
+ if (lDesc[:PluginInitCode] != nil)
253
+ lDesc[:PluginInitCode].call(lPlugin)
254
+ end
255
+ rescue Exception
256
+ rError = FailedPluginError.new("Error while loading file #{lDesc[:PluginFileName]} and instantiating #{lDesc[:PluginClassName]}: #{$!}. Ignoring this plugin.")
257
+ end
258
+ end
259
+ end
260
+ end
261
+ rPlugin = lDesc[:PluginInstance]
262
+ end
263
+ end
264
+
265
+ return rPlugin, rError
266
+ end
267
+
268
+ # Get the named plugin description
269
+ #
270
+ # Parameters:
271
+ # * *iCategory* (_Object_): Category those plugins will belong to
272
+ # * *iPluginName* (_String_): Plugin name
273
+ # Return:
274
+ # * <em>map<Symbol,Object></em>: The corresponding description, or nil in case of failure
275
+ def getPluginDescription(iCategory, iPluginName)
276
+ rDesc = nil
277
+
278
+ if (@Plugins[iCategory] == nil)
279
+ logErr "Unknown plugins category #{iCategory}."
280
+ else
281
+ rDesc = @Plugins[iCategory][iPluginName]
282
+ if (rDesc == nil)
283
+ logErr "Unknown plugin #{iPluginName} in category #{iCategory}."
284
+ end
285
+ end
286
+
287
+ return rDesc
288
+ end
289
+
290
+ # Give access to a plugin.
291
+ # An exception is thrown if the plugin does not exist.
292
+ #
293
+ # Parameters:
294
+ # * *iCategoryName* (_String_): Category of the plugin to access
295
+ # * *iPluginName* (_String_): Name of the plugin to access
296
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
297
+ # ** *OnlyIfExtDepsResolved* (_Boolean_): Do we return the plugin only if there is no need to install external dependencies ? [optional = false]
298
+ # ** *RDIInstaller* (<em>RDI::Installer</em>): The RDI installer if available, or nil otherwise [optional = nil]
299
+ # ** *RDIContextModifiers* (<em>map<String,list<[String,Object]>></em>): The map of context modifiers to be filled by the RDI installer if specified, or nil if ignored [optional = nil]
300
+ # * *CodeBlock*: The code called when the plugin is found:
301
+ # ** *ioPlugin* (_Object_): The corresponding plugin
302
+ def accessPlugin(iCategoryName, iPluginName, iParameters = {})
303
+ lPlugin, lError = getPluginInstance(iCategoryName, iPluginName, iParameters)
304
+ if (lPlugin == nil)
305
+ raise lError
306
+ else
307
+ yield(lPlugin)
308
+ end
309
+ end
310
+
311
+ # Clear the registered plugins
312
+ def clearPlugins
313
+ @Plugins = {}
314
+ end
315
+
316
+ # Get the list of plugin names of a given category
317
+ #
318
+ # Parameters:
319
+ # * *iCategoryName* (_String_): The category for which we want the plugin names list
320
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
321
+ # ** *IncludeDisabled* (_Boolean_): Do we include disabled plugins ? [optional = false]
322
+ # Return:
323
+ # * <em>list<String></em>: The list of plugin names in this category
324
+ def getPluginNames(iCategoryName, iParameters = {})
325
+ rPlugins = []
326
+
327
+ lIncludeDisabled = iParameters[:IncludeDisabled]
328
+ if (lIncludeDisabled == nil)
329
+ lIncludeDisabled = false
330
+ end
331
+ if (@Plugins[iCategoryName] != nil)
332
+ @Plugins[iCategoryName].each do |iPluginName, iPluginDesc|
333
+ if ((lIncludeDisabled) or
334
+ (iPluginDesc[:Enabled] != false))
335
+ rPlugins << iPluginName
336
+ end
337
+ end
338
+ end
339
+
340
+ return rPlugins
341
+ end
342
+
343
+ # Get the map of plugins descriptions, indexed with plugin names
344
+ #
345
+ # Parameters:
346
+ # * *iCategoryName* (_String_): The category for which we want the plugin names list
347
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
348
+ # ** *IncludeDisabled* (_Boolean_): Do we include disabled plugins ? [optional = false]
349
+ # Return:
350
+ # * <em>map<String,map<Symbol,Object>></em>: The map of plugin descriptions per plugin name
351
+ def getPluginsDescriptions(iCategoryName, iParameters = {})
352
+ rPlugins = {}
353
+
354
+ lIncludeDisabled = iParameters[:IncludeDisabled]
355
+ if (lIncludeDisabled == nil)
356
+ lIncludeDisabled = false
357
+ end
358
+ if (@Plugins[iCategoryName] != nil)
359
+ if (lIncludeDisabled)
360
+ rPlugins = @Plugins[iCategoryName]
361
+ else
362
+ @Plugins[iCategoryName].each do |iPluginName, iPluginDesc|
363
+ if (iPluginDesc[:Enabled] != false)
364
+ rPlugins[iPluginName] = iPluginDesc
365
+ end
366
+ end
367
+ end
368
+ end
369
+
370
+ return rPlugins
371
+ end
372
+
373
+ end
374
+
375
+ # Initialize a plugins singleton
376
+ def self.initializePlugins
377
+ $rUtilAnts_Plugins_Manager = PluginsManager.new
378
+ Object.module_eval('include RUtilAnts::Plugins')
379
+ end
380
+
381
+ # Register a new plugin
382
+ #
383
+ # Parameters:
384
+ # * *iCategoryName* (_String_): Category this plugin belongs to
385
+ # * *iPluginName* (_String_): Plugin name
386
+ # * *iFileName* (_String_): File name containing the plugin (can be nil)
387
+ # * *iDesc* (<em>map<Symbol,Object></em>): Plugin's description (can be nil)
388
+ # * *iClassName* (_String_): Name of the plugin class
389
+ # * *iInitCodeBlock* (_Proc_): Code block to call when initializing the real instance (can be nil)
390
+ def registerNewPlugin(iCategoryName, iPluginName, iFileName, iDesc, iClassName, iInitCodeBlock)
391
+ $rUtilAnts_Plugins_Manager.registerNewPlugin(iCategoryName, iPluginName, iFileName, iDesc, iClassName, iInitCodeBlock)
392
+ end
393
+
394
+ # Parse plugins from a given directory
395
+ #
396
+ # Parameters:
397
+ # * *iCategory* (_Object_): Category those plugins will belong to
398
+ # * *iDir* (_String_): Directory to parse for plugins
399
+ # * *iBaseClassNames* (_String_): The base class name of plugins to be instantiated
400
+ def parsePluginsFromDir(iCategory, iDir, iBaseClassNames)
401
+ $rUtilAnts_Plugins_Manager.parsePluginsFromDir(iCategory, iDir, iBaseClassNames)
402
+ end
403
+
404
+ # Get the named plugin instance
405
+ #
406
+ # Parameters:
407
+ # * *iCategory* (_Object_): Category those plugins will belong to
408
+ # * *iPluginName* (_String_): Plugin name
409
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
410
+ # ** *OnlyIfExtDepsResolved* (_Boolean_): Do we return the plugin only if there is no need to install external dependencies ? [optional = false]
411
+ # ** *RDIInstaller* (<em>RDI::Installer</em>): The RDI installer if available, or nil otherwise [optional = nil]
412
+ # ** *RDIContextModifiers* (<em>map<String,list<[String,Object]>></em>): The map of context modifiers to be filled by the RDI installer if specified, or nil if ignored [optional = nil]
413
+ # Return:
414
+ # * _Object_: The corresponding plugin, or nil in case of failure
415
+ # * _Exception_: The error, or nil in case of success
416
+ def getPluginInstance(iCategory, iPluginName, iParameters = {})
417
+ return $rUtilAnts_Plugins_Manager.getPluginInstance(iCategory, iPluginName, iParameters)
418
+ end
419
+
420
+ # Get the named plugin description
421
+ #
422
+ # Parameters:
423
+ # * *iCategory* (_Object_): Category those plugins will belong to
424
+ # * *iPluginName* (_String_): Plugin name
425
+ # Return:
426
+ # * <em>map<Symbol,Object></em>: The corresponding description, or nil in case of failure
427
+ def getPluginDescription(iCategory, iPluginName)
428
+ return $rUtilAnts_Plugins_Manager.getPluginDescription(iCategory, iPluginName)
429
+ end
430
+
431
+ # Give access to a plugin.
432
+ # An exception is thrown if the plugin does not exist.
433
+ #
434
+ # Parameters:
435
+ # * *iCategoryName* (_String_): Category of the plugin to access
436
+ # * *iPluginName* (_String_): Name of the plugin to access
437
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
438
+ # ** *OnlyIfExtDepsResolved* (_Boolean_): Do we return the plugin only if there is no need to install external dependencies ? [optional = false]
439
+ # ** *RDIInstaller* (<em>RDI::Installer</em>): The RDI installer if available, or nil otherwise [optional = nil]
440
+ # ** *RDIContextModifiers* (<em>map<String,list<[String,Object]>></em>): The map of context modifiers to be filled by the RDI installer if specified, or nil if ignored [optional = nil]
441
+ # * *CodeBlock*: The code called when the plugin is found:
442
+ # ** *ioPlugin* (_Object_): The corresponding plugin
443
+ def accessPlugin(iCategoryName, iPluginName, iParameters = {})
444
+ $rUtilAnts_Plugins_Manager.accessPlugin(iCategoryName, iPluginName, iParameters) do |ioPlugin|
445
+ yield(ioPlugin)
446
+ end
447
+ end
448
+
449
+ # Clear the registered plugins
450
+ def clearPlugins
451
+ $rUtilAnts_Plugins_Manager.clearPlugins
452
+ end
453
+
454
+ # Get the list of plugin names of a given category
455
+ #
456
+ # Parameters:
457
+ # * *iCategoryName* (_String_): The category for which we want the plugin names list
458
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
459
+ # ** *IncludeDisabled* (_Boolean_): Do we include disabled plugins ? [optional = false]
460
+ # Return:
461
+ # * <em>list<String></em>: The list of plugin names in this category
462
+ def getPluginNames(iCategoryName, iParameters = {})
463
+ return $rUtilAnts_Plugins_Manager.getPluginNames(iCategoryName, iParameters)
464
+ end
465
+
466
+ # Get the map of plugins descriptions, indexed with plugin names
467
+ #
468
+ # Parameters:
469
+ # * *iCategoryName* (_String_): The category for which we want the plugin names list
470
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
471
+ # ** *IncludeDisabled* (_Boolean_): Do we include disabled plugins ? [optional = false]
472
+ # Return:
473
+ # * <em>map<String,map<Symbol,Object>></em>: The map of plugin descriptions per plugin name
474
+ def getPluginsDescriptions(iCategoryName, iParameters = {})
475
+ return $rUtilAnts_Plugins_Manager.getPluginsDescriptions(iCategoryName, iParameters)
476
+ end
477
+
478
+ end
479
+
480
+ end