|
@@ -33,7 +33,7 @@ LodelContext.expose_modules(globals(), {
|
33
|
33
|
#
|
34
|
34
|
#@todo Check if the symlink in the lodelcontext dir is obsolete !!!
|
35
|
35
|
#@warning The plugins dir is at two locations : in lodel package and in
|
36
|
|
-#instance directory. Some stuff seems to still needs plugins to be in
|
|
36
|
+#instance directory. Some stuff seems to still needs plugins to be in
|
37
|
37
|
#the instance directory but it seems to be a really bad idea...
|
38
|
38
|
|
39
|
39
|
##@defgroup plugin_init_specs Plugins __init__.py specifications
|
|
@@ -67,7 +67,7 @@ PLUGINS_PATH = os.path.join(LodelContext.context_dir(),'plugins')
|
67
|
67
|
|
68
|
68
|
##@brief List storing the mandatory variables expected in a plugin __init__.py
|
69
|
69
|
#file
|
70
|
|
-MANDATORY_VARNAMES = [PLUGIN_NAME_VARNAME, LOADER_FILENAME_VARNAME,
|
|
70
|
+MANDATORY_VARNAMES = [PLUGIN_NAME_VARNAME, LOADER_FILENAME_VARNAME,
|
71
|
71
|
PLUGIN_VERSION_VARNAME]
|
72
|
72
|
|
73
|
73
|
##@brief Default plugin type
|
|
@@ -122,7 +122,7 @@ version number" % arg)
|
122
|
122
|
elif len(args) > 3:
|
123
|
123
|
raise PluginError("Expected between 1 and 3 positional arguments \
|
124
|
124
|
but %d arguments found" % len(args))
|
125
|
|
- else:
|
|
125
|
+ else:
|
126
|
126
|
for i,v in enumerate(args):
|
127
|
127
|
self.__version[i] = int(v)
|
128
|
128
|
#Checks that version numbering is correct
|
|
@@ -144,7 +144,7 @@ but %d arguments found" % len(args))
|
144
|
144
|
@property
|
145
|
145
|
def revision(self):
|
146
|
146
|
return self.__version[2]
|
147
|
|
-
|
|
147
|
+
|
148
|
148
|
##@brief Check and prepare comparisoon argument
|
149
|
149
|
#@return A PluginVersion instance
|
150
|
150
|
#@throw PluginError if invalid argument provided
|
|
@@ -157,7 +157,7 @@ but %d arguments found" % len(args))
|
157
|
157
|
raise PluginError("Cannot compare argument '%s' with \
|
158
|
158
|
a PluginVerison instance" % other)
|
159
|
159
|
return other
|
160
|
|
-
|
|
160
|
+
|
161
|
161
|
##@brief Allow accessing to versions parts using interger index
|
162
|
162
|
#@param key int : index
|
163
|
163
|
#@return major for key == 0, minor for key == 1, revision for key == 2
|
|
@@ -213,7 +213,7 @@ a PluginVerison instance" % other)
|
213
|
213
|
#
|
214
|
214
|
#Automatic script registration on child class declaration
|
215
|
215
|
class MetaPlugType(type):
|
216
|
|
-
|
|
216
|
+
|
217
|
217
|
##@brief Dict storing all plugin types
|
218
|
218
|
#
|
219
|
219
|
#key is the _type_conf_name and value is the class
|
|
@@ -227,20 +227,20 @@ class MetaPlugType(type):
|
227
|
227
|
return
|
228
|
228
|
#Regitering a new plugin type
|
229
|
229
|
MetaPlugType._all_ptypes[self._type_conf_name] = self
|
230
|
|
-
|
|
230
|
+
|
231
|
231
|
##@brief Accessor to the list of plugin types
|
232
|
232
|
#@return A copy of _all_ptypes attribute (a dict with typename as key
|
233
|
233
|
#and the class as value)
|
234
|
234
|
@classmethod
|
235
|
235
|
def all_types(cls):
|
236
|
236
|
return copy.copy(cls._all_ptypes)
|
237
|
|
-
|
|
237
|
+
|
238
|
238
|
##@brief Accessor to the list of plugin names
|
239
|
239
|
#@return a list of plugin name
|
240
|
240
|
@classmethod
|
241
|
241
|
def all_ptype_names(cls):
|
242
|
242
|
return list(cls._all_ptypes.keys())
|
243
|
|
-
|
|
243
|
+
|
244
|
244
|
##@brief Given a plugin type name return a Plugin child class
|
245
|
245
|
#@param ptype_name str : a plugin type name
|
246
|
246
|
#@return A Plugin child class
|
|
@@ -250,13 +250,13 @@ class MetaPlugType(type):
|
250
|
250
|
if ptype_name not in cls._all_ptypes:
|
251
|
251
|
raise PluginError("Unknown plugin type '%s'" % ptype_name)
|
252
|
252
|
return cls._all_ptypes[ptype_name]
|
253
|
|
-
|
|
253
|
+
|
254
|
254
|
##@brief Call the clear classmethod on each child classes
|
255
|
255
|
@classmethod
|
256
|
256
|
def clear_cls(cls):
|
257
|
257
|
for pcls in cls._all_ptypes.values():
|
258
|
258
|
pcls.clear_cls()
|
259
|
|
-
|
|
259
|
+
|
260
|
260
|
|
261
|
261
|
##@brief Handle plugins
|
262
|
262
|
#@ingroup lodel2_plugins
|
|
@@ -269,17 +269,17 @@ class MetaPlugType(type):
|
269
|
269
|
# 2. Settings fetch all confspecs
|
270
|
270
|
# 3. the loader call load_all to register hooks etc
|
271
|
271
|
class Plugin(object, metaclass=MetaPlugType):
|
272
|
|
-
|
|
272
|
+
|
273
|
273
|
##@brief Stores Plugin instances indexed by name
|
274
|
274
|
_plugin_instances = dict()
|
275
|
|
-
|
|
275
|
+
|
276
|
276
|
##@brief Attribute used by load_all and load methods to detect circular
|
277
|
277
|
#dependencies
|
278
|
278
|
_load_called = []
|
279
|
279
|
|
280
|
280
|
##@brief Attribute that stores plugins list from discover cache file
|
281
|
281
|
_plugin_list = None
|
282
|
|
-
|
|
282
|
+
|
283
|
283
|
#@brief Designed to store, in child classes, the confspec indicating \
|
284
|
284
|
#where plugin list is stored
|
285
|
285
|
_plist_confspecs = None
|
|
@@ -303,12 +303,12 @@ class Plugin(object, metaclass=MetaPlugType):
|
303
|
303
|
# @param plugin_name str : plugin name
|
304
|
304
|
# @throw PluginError
|
305
|
305
|
def __init__(self, plugin_name):
|
306
|
|
-
|
|
306
|
+
|
307
|
307
|
##@brief The plugin name
|
308
|
308
|
self.name = plugin_name
|
309
|
309
|
##@brief The plugin package path
|
310
|
310
|
self.path = self.plugin_path(plugin_name)
|
311
|
|
-
|
|
311
|
+
|
312
|
312
|
##@brief Stores the plugin module
|
313
|
313
|
self.module = None
|
314
|
314
|
##@brief Stores the plugin loader module
|
|
@@ -317,7 +317,7 @@ class Plugin(object, metaclass=MetaPlugType):
|
317
|
317
|
self.__confspecs = dict()
|
318
|
318
|
##@brief Boolean flag telling if the plugin is loaded or not
|
319
|
319
|
self.loaded = False
|
320
|
|
-
|
|
320
|
+
|
321
|
321
|
# Importing __init__.py infos in it
|
322
|
322
|
plugin_module = self.module_name()
|
323
|
323
|
self.module = LodelContext.module(plugin_module)
|
|
@@ -398,7 +398,7 @@ name differ from the one found in plugin's init file"
|
398
|
398
|
#@throw PluginError if the filename was not valid
|
399
|
399
|
#@todo Some strange things append :
|
400
|
400
|
#when loading modules in test self.module.__name__ does not contains
|
401
|
|
- #the package... but in prod cases the self.module.__name__ is
|
|
401
|
+ #the package... but in prod cases the self.module.__name__ is
|
402
|
402
|
#the module fullname... Just a reminder note to explain the dirty
|
403
|
403
|
#if on self_modname
|
404
|
404
|
def _import_from_init_var(self, varname):
|
|
@@ -428,14 +428,12 @@ name differ from the one found in plugin's init file"
|
428
|
428
|
base_mod = '.'.join(filename.split('.')[:-1])
|
429
|
429
|
module_name = self_modname+"."+base_mod
|
430
|
430
|
return importlib.import_module(module_name)
|
431
|
|
-
|
|
431
|
+
|
432
|
432
|
##@brief Return associated module name
|
433
|
433
|
def module_name(self):
|
434
|
|
- path_array = self.path.split('/')
|
435
|
|
- if not self.path.startswith('./plugins'):
|
436
|
|
- raise PluginError("Bad path for plugin %s : %s" % (
|
437
|
|
- self.name, self.path))
|
438
|
|
- return '.'.join(['lodel'] + path_array[path_array.index('plugins'):])
|
|
434
|
+ #WOOT WE LOVE DIRTY WORKAROUNDS !!!
|
|
435
|
+ return "lodel.plugins."+os.path.basename(self.path)
|
|
436
|
+ #END OF DIRTY WORKAROUND
|
439
|
437
|
|
440
|
438
|
##@brief Check dependencies of plugin
|
441
|
439
|
#@return A list of plugin name to be loaded before
|
|
@@ -455,7 +453,7 @@ name differ from the one found in plugin's init file"
|
455
|
453
|
raise PluginError( "Bad dependencie for '%s' :"%self.name,
|
456
|
454
|
', '.join(errors))
|
457
|
455
|
return result
|
458
|
|
-
|
|
456
|
+
|
459
|
457
|
##@brief Check if the plugin should be activated
|
460
|
458
|
#
|
461
|
459
|
#Try to fetch a function called @ref ACTIVATE_METHOD_NAME in __init__.py
|
|
@@ -473,10 +471,10 @@ name differ from the one found in plugin's init file"
|
473
|
471
|
logger.debug(msg)
|
474
|
472
|
test_fun = lambda:True
|
475
|
473
|
return test_fun()
|
476
|
|
-
|
|
474
|
+
|
477
|
475
|
##@brief Load a plugin
|
478
|
476
|
#
|
479
|
|
- #Loading a plugin means importing a file. The filename is defined in the
|
|
477
|
+ #Loading a plugin means importing a file. The filename is defined in the
|
480
|
478
|
#plugin's __init__.py file in a LOADER_FILENAME_VARNAME variable.
|
481
|
479
|
#
|
482
|
480
|
#The loading process has to take care of other things :
|
|
@@ -498,7 +496,7 @@ name differ from the one found in plugin's init file"
|
498
|
496
|
raise PluginError("Circular dependencie in Plugin detected. Abording")
|
499
|
497
|
else:
|
500
|
498
|
self._load_called.append(self.name)
|
501
|
|
-
|
|
499
|
+
|
502
|
500
|
#Dependencie load
|
503
|
501
|
for dependencie in self.check_deps():
|
504
|
502
|
activable = dependencie.activable()
|
|
@@ -510,7 +508,7 @@ name differ from the one found in plugin's init file"
|
510
|
508
|
plugin_name = self.name,
|
511
|
509
|
dep_name = dependencie.name,
|
512
|
510
|
reason = activable)
|
513
|
|
-
|
|
511
|
+
|
514
|
512
|
#Loading the plugin
|
515
|
513
|
try:
|
516
|
514
|
self.__loader_module = self._import_from_init_var(LOADER_FILENAME_VARNAME)
|
|
@@ -524,7 +522,7 @@ name differ from the one found in plugin's init file"
|
524
|
522
|
raise PluginError(msg)
|
525
|
523
|
logger.debug("Plugin '%s' loaded" % self.name)
|
526
|
524
|
self.loaded = True
|
527
|
|
-
|
|
525
|
+
|
528
|
526
|
##@brief Returns the loader module
|
529
|
527
|
#
|
530
|
528
|
#Accessor for the __loader__ python module
|
|
@@ -559,7 +557,7 @@ name differ from the one found in plugin's init file"
|
559
|
557
|
raise PluginError(msg)
|
560
|
558
|
LodelHook.call_hook(
|
561
|
559
|
"lodel2_plugins_loaded", cls, cls._plugin_instances)
|
562
|
|
-
|
|
560
|
+
|
563
|
561
|
|
564
|
562
|
##@return a copy of __confspecs attr
|
565
|
563
|
@property
|
|
@@ -593,7 +591,7 @@ name differ from the one found in plugin's init file"
|
593
|
591
|
return res
|
594
|
592
|
|
595
|
593
|
##@brief Register a new plugin
|
596
|
|
- #
|
|
594
|
+ #
|
597
|
595
|
#@param plugin_name str : The plugin name
|
598
|
596
|
#@return a Plugin instance
|
599
|
597
|
#@throw PluginError
|
|
@@ -627,7 +625,7 @@ name differ from the one found in plugin's init file"
|
627
|
625
|
msg = "No plugin named '%s' loaded"
|
628
|
626
|
msg %= plugin_name
|
629
|
627
|
raise PluginError(msg)
|
630
|
|
-
|
|
628
|
+
|
631
|
629
|
##@brief Given a plugin name returns the plugin path
|
632
|
630
|
# @param plugin_name str : The plugin name
|
633
|
631
|
# @return the plugin directory path
|
|
@@ -643,7 +641,7 @@ name differ from the one found in plugin's init file"
|
643
|
641
|
pass
|
644
|
642
|
|
645
|
643
|
return plist[plugin_name]['path']
|
646
|
|
-
|
|
644
|
+
|
647
|
645
|
##@brief Return the plugin module name
|
648
|
646
|
#
|
649
|
647
|
#This module name is the "virtual" module where we imported the plugin.
|
|
@@ -659,7 +657,7 @@ name differ from the one found in plugin's init file"
|
659
|
657
|
return "%s.%s" % (VIRTUAL_PACKAGE_NAME, plugin_name)
|
660
|
658
|
|
661
|
659
|
##@brief Start the Plugin class
|
662
|
|
- #
|
|
660
|
+ #
|
663
|
661
|
# Called by Settings.__bootstrap()
|
664
|
662
|
#
|
665
|
663
|
# This method load path and preload plugins
|
|
@@ -667,7 +665,7 @@ name differ from the one found in plugin's init file"
|
667
|
665
|
def start(cls, plugins):
|
668
|
666
|
for plugin_name in plugins:
|
669
|
667
|
cls.register(plugin_name)
|
670
|
|
-
|
|
668
|
+
|
671
|
669
|
##@brief Attempt to "restart" the Plugin class
|
672
|
670
|
@classmethod
|
673
|
671
|
def clear(cls):
|
|
@@ -676,12 +674,12 @@ name differ from the one found in plugin's init file"
|
676
|
674
|
if cls._load_called != []:
|
677
|
675
|
cls._load_called = []
|
678
|
676
|
MetaPlugType.clear_cls()
|
679
|
|
-
|
|
677
|
+
|
680
|
678
|
##@brief Designed to be implemented by child classes
|
681
|
679
|
@classmethod
|
682
|
680
|
def clear_cls(cls):
|
683
|
681
|
pass
|
684
|
|
-
|
|
682
|
+
|
685
|
683
|
##@brief Reccursively walk throught paths to find plugin, then stores
|
686
|
684
|
#found plugin in a static var
|
687
|
685
|
#
|
|
@@ -699,7 +697,7 @@ name differ from the one found in plugin's init file"
|
699
|
697
|
result = dict()
|
700
|
698
|
for pinfos in tmp_res:
|
701
|
699
|
pname = pinfos['name']
|
702
|
|
- if ( pname in result
|
|
700
|
+ if ( pname in result
|
703
|
701
|
and pinfos['version'] > result[pname]['version'])\
|
704
|
702
|
or pname not in result:
|
705
|
703
|
result[pname] = pinfos
|
|
@@ -708,7 +706,7 @@ name differ from the one found in plugin's init file"
|
708
|
706
|
pass
|
709
|
707
|
cls._plugin_list = result
|
710
|
708
|
return result
|
711
|
|
-
|
|
709
|
+
|
712
|
710
|
##@brief Return discover result
|
713
|
711
|
#@param refresh bool : if true invalidate all plugin list cache
|
714
|
712
|
#@note If discover cache file not found run discover first
|
|
@@ -780,7 +778,7 @@ name differ from the one found in plugin's init file"
|
780
|
778
|
'version': PluginVersion(pversion),
|
781
|
779
|
'path': path,
|
782
|
780
|
'type': ptype}
|
783
|
|
-
|
|
781
|
+
|
784
|
782
|
##@brief Import init file from a plugin path
|
785
|
783
|
#@param path str : Directory path
|
786
|
784
|
#@return a tuple (init_module, module_name)
|
|
@@ -857,7 +855,7 @@ class CustomMethod(object):
|
857
|
855
|
##@brief Decorator constructor
|
858
|
856
|
#@param component_name str : the name of the component to enhance
|
859
|
857
|
#@param method_name str : the name of the method to inject (if None given
|
860
|
|
- #@param method_type int : take value in one of
|
|
858
|
+ #@param method_type int : take value in one of
|
861
|
859
|
#CustomMethod::INSTANCE_METHOD CustomMethod::CLASS_METHOD or
|
862
|
860
|
#CustomMethod::STATIC_METHOD
|
863
|
861
|
#use the function name
|
|
@@ -875,7 +873,7 @@ class CustomMethod(object):
|
875
|
873
|
raise ValueError("Excepted value for method_type was one of \
|
876
|
874
|
CustomMethod::INSTANCE_METHOD CustomMethod::CLASS_METHOD or \
|
877
|
875
|
CustomMethod::STATIC_METHOD, but got %s" % self._type)
|
878
|
|
-
|
|
876
|
+
|
879
|
877
|
##@brief called just after __init__
|
880
|
878
|
#@param fun function : the decorated function
|
881
|
879
|
def __call__(self, fun):
|
|
@@ -891,7 +889,7 @@ another plugin : %s" % (
|
891
|
889
|
self._custom_methods[self._comp_name].__module__))
|
892
|
890
|
self._fun = fun
|
893
|
891
|
self._custom_methods[self._comp_name].append(self)
|
894
|
|
-
|
|
892
|
+
|
895
|
893
|
##@brief Textual representation
|
896
|
894
|
#@return textual representation of the CustomMethod instance
|
897
|
895
|
def __repr__(self):
|
|
@@ -902,7 +900,7 @@ source={module_name}.{fun_name}>"
|
902
|
900
|
classname = self._comp_name,
|
903
|
901
|
module_name = self._fun.__module__,
|
904
|
902
|
fun_name = self._fun.__name__)
|
905
|
|
-
|
|
903
|
+
|
906
|
904
|
##@brief Return a well formed method
|
907
|
905
|
#
|
908
|
906
|
#@note the type of method depends on the _type attribute
|
|
@@ -924,7 +922,7 @@ CustomMethod::STATIC_METHOD")
|
924
|
922
|
|
925
|
923
|
##@brief Handle custom method dynamic injection in LeAPI dyncode
|
926
|
924
|
#
|
927
|
|
- #Called by lodel2_dyncode_loaded hook defined at
|
|
925
|
+ #Called by lodel2_dyncode_loaded hook defined at
|
928
|
926
|
#lodel.plugin.core_hooks.lodel2_plugin_custom_methods()
|
929
|
927
|
#
|
930
|
928
|
#@param cls
|