rUtilAnts 0.1.0.20091014

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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