32 class PluginManager(QObject): |
33 class PluginManager(QObject): |
33 """ |
34 """ |
34 Class implementing the Plugin Manager. |
35 Class implementing the Plugin Manager. |
35 |
36 |
36 @signal shutdown() emitted at shutdown of the IDE |
37 @signal shutdown() emitted at shutdown of the IDE |
37 @signal pluginAboutToBeActivated(modulName, pluginObject) emitted just before a |
38 @signal pluginAboutToBeActivated(modulName, pluginObject) emitted just |
38 plugin is activated |
39 before a plugin is activated |
39 @signal pluginActivated(modulName, pluginObject) emitted just after a plugin |
40 @signal pluginActivated(modulName, pluginObject) emitted just after |
40 was activated |
41 a plugin was activated |
41 @signal allPlugginsActivated() emitted at startup after all plugins have |
42 @signal allPlugginsActivated() emitted at startup after all plugins have |
42 been activated |
43 been activated |
43 @signal pluginAboutToBeDeactivated(modulName, pluginObject) emitted just before a |
44 @signal pluginAboutToBeDeactivated(modulName, pluginObject) emitted just |
44 plugin is deactivated |
45 before a plugin is deactivated |
45 @signal pluginDeactivated(modulName, pluginObject) emitted just after a plugin |
46 @signal pluginDeactivated(modulName, pluginObject) emitted just after |
46 was deactivated |
47 a plugin was deactivated |
47 """ |
48 """ |
48 shutdown = pyqtSignal() |
49 shutdown = pyqtSignal() |
49 pluginAboutToBeActivated = pyqtSignal(str, object) |
50 pluginAboutToBeActivated = pyqtSignal(str, object) |
50 pluginActivated = pyqtSignal(str, object) |
51 pluginActivated = pyqtSignal(str, object) |
51 allPlugginsActivated = pyqtSignal() |
52 allPlugginsActivated = pyqtSignal() |
190 del self.pluginDirs["user"] |
194 del self.pluginDirs["user"] |
191 del self.pluginDirs["global"] |
195 del self.pluginDirs["global"] |
192 |
196 |
193 if not os.path.exists(self.pluginDirs["eric5"]): |
197 if not os.path.exists(self.pluginDirs["eric5"]): |
194 return (False, |
198 return (False, |
195 self.trUtf8("The internal plugin directory <b>{0}</b> does not exits.")\ |
199 self.trUtf8( |
196 .format(self.pluginDirs["eric5"])) |
200 "The internal plugin directory <b>{0}</b>" |
|
201 " does not exits.").format(self.pluginDirs["eric5"])) |
197 |
202 |
198 return (True, "") |
203 return (True, "") |
199 |
204 |
200 def __pluginModulesExist(self): |
205 def __pluginModulesExist(self): |
201 """ |
206 """ |
202 Private method to check, if there are plugins available. |
207 Private method to check, if there are plugins available. |
203 |
208 |
204 @return flag indicating the availability of plugins (boolean) |
209 @return flag indicating the availability of plugins (boolean) |
205 """ |
210 """ |
206 if self.__develPluginFile and not os.path.exists(self.__develPluginFile): |
211 if self.__develPluginFile and \ |
|
212 not os.path.exists(self.__develPluginFile): |
207 return False |
213 return False |
208 |
214 |
209 self.__foundCoreModules = self.getPluginModules(self.pluginDirs["eric5"]) |
215 self.__foundCoreModules = self.getPluginModules( |
|
216 self.pluginDirs["eric5"]) |
210 if "global" in self.pluginDirs: |
217 if "global" in self.pluginDirs: |
211 self.__foundGlobalModules = \ |
218 self.__foundGlobalModules = \ |
212 self.getPluginModules(self.pluginDirs["global"]) |
219 self.getPluginModules(self.pluginDirs["global"]) |
213 if "user" in self.pluginDirs: |
220 if "user" in self.pluginDirs: |
214 self.__foundUserModules = \ |
221 self.__foundUserModules = \ |
297 checks are added to the failed modules list. |
304 checks are added to the failed modules list. |
298 |
305 |
299 @param name name of the module to be loaded (string) |
306 @param name name of the module to be loaded (string) |
300 @param directory name of the plugin directory (string) |
307 @param directory name of the plugin directory (string) |
301 @param reload_ flag indicating to reload the module (boolean) |
308 @param reload_ flag indicating to reload the module (boolean) |
|
309 @exception PluginLoadError raised to indicate an issue loading |
|
310 the plug-in |
302 """ |
311 """ |
303 try: |
312 try: |
304 fname = "{0}.py".format(os.path.join(directory, name)) |
313 fname = "{0}.py".format(os.path.join(directory, name)) |
305 module = imp.load_source(name, fname) |
314 module = imp.load_source(name, fname) |
306 if not hasattr(module, "autoactivate"): |
315 if not hasattr(module, "autoactivate"): |
307 module.error = \ |
316 module.error = self.trUtf8( |
308 self.trUtf8("Module is missing the 'autoactivate' attribute.") |
317 "Module is missing the 'autoactivate' attribute.") |
309 self.__failedModules[name] = module |
318 self.__failedModules[name] = module |
310 raise PluginLoadError(name) |
319 raise PluginLoadError(name) |
311 if getattr(module, "autoactivate"): |
320 if getattr(module, "autoactivate"): |
312 self.__inactiveModules[name] = module |
321 self.__inactiveModules[name] = module |
313 else: |
322 else: |
365 del self.__failedModules[name] |
374 del self.__failedModules[name] |
366 |
375 |
367 self.__modulesCount -= 1 |
376 self.__modulesCount -= 1 |
368 return True |
377 return True |
369 |
378 |
370 def removePluginFromSysModules(self, pluginName, package, internalPackages): |
379 def removePluginFromSysModules(self, pluginName, package, |
371 """ |
380 internalPackages): |
372 Public method to remove a plugin and all related modules from sys.modules. |
381 """ |
|
382 Public method to remove a plugin and all related modules from |
|
383 sys.modules. |
373 |
384 |
374 @param pluginName name of the plugin module (string) |
385 @param pluginName name of the plugin module (string) |
375 @param package name of the plugin package (string) |
386 @param package name of the plugin package (string) |
376 @param internalPackages list of intenal packages (list of string) |
387 @param internalPackages list of intenal packages (list of string) |
377 @return flag indicating the plugin module was found in sys.modules (boolean) |
388 @return flag indicating the plugin module was found in sys.modules |
|
389 (boolean) |
378 """ |
390 """ |
379 packages = [package] + internalPackages |
391 packages = [package] + internalPackages |
380 found = False |
392 found = False |
381 if not package: |
393 if not package: |
382 package = "__None__" |
394 package = "__None__" |
383 for moduleName in list(sys.modules.keys())[:]: |
395 for moduleName in list(sys.modules.keys())[:]: |
384 if moduleName == pluginName or moduleName.split(".")[0] in packages: |
396 if moduleName == pluginName or \ |
|
397 moduleName.split(".")[0] in packages: |
385 found = True |
398 found = True |
386 del sys.modules[moduleName] |
399 del sys.modules[moduleName] |
387 return found |
400 return found |
388 |
401 |
389 def initOnDemandPlugins(self): |
402 def initOnDemandPlugins(self): |
398 |
411 |
399 def initOnDemandPlugin(self, name): |
412 def initOnDemandPlugin(self, name): |
400 """ |
413 """ |
401 Public method to create a plugin object for the named on demand plugin. |
414 Public method to create a plugin object for the named on demand plugin. |
402 |
415 |
403 Note: The plugin is not activated. |
416 Note: The plug-in is not activated. |
|
417 |
|
418 @param name name of the plug-in (string) |
|
419 @exception PluginActivationError raised to indicate an issue during the |
|
420 plug-in activation |
404 """ |
421 """ |
405 try: |
422 try: |
406 try: |
423 try: |
407 module = self.__onDemandInactiveModules[name] |
424 module = self.__onDemandInactiveModules[name] |
408 except KeyError: |
425 except KeyError: |
409 return None |
426 return |
410 |
427 |
411 if not self.__canActivatePlugin(module): |
428 if not self.__canActivatePlugin(module): |
412 raise PluginActivationError(module.eric5PluginModuleName) |
429 raise PluginActivationError(module.eric5PluginModuleName) |
413 version = getattr(module, "version") |
430 version = getattr(module, "version") |
414 className = getattr(module, "className") |
431 className = getattr(module, "className") |
419 pluginObject.eric5PluginModule = module |
436 pluginObject.eric5PluginModule = module |
420 pluginObject.eric5PluginName = className |
437 pluginObject.eric5PluginName = className |
421 pluginObject.eric5PluginVersion = version |
438 pluginObject.eric5PluginVersion = version |
422 self.__onDemandInactivePlugins[name] = pluginObject |
439 self.__onDemandInactivePlugins[name] = pluginObject |
423 except PluginActivationError: |
440 except PluginActivationError: |
424 return None |
441 return |
425 |
442 |
426 def activatePlugins(self): |
443 def activatePlugins(self): |
427 """ |
444 """ |
428 Public method to activate all plugins having the "autoactivate" attribute |
445 Public method to activate all plugins having the "autoactivate" |
429 set to True. |
446 attribute set to True. |
430 """ |
447 """ |
431 savedInactiveList = Preferences.Prefs.settings.value(self.__inactivePluginsKey) |
448 savedInactiveList = Preferences.Prefs.settings.value( |
|
449 self.__inactivePluginsKey) |
432 if self.__develPluginName is not None and \ |
450 if self.__develPluginName is not None and \ |
433 savedInactiveList is not None and \ |
451 savedInactiveList is not None and \ |
434 self.__develPluginName in savedInactiveList: |
452 self.__develPluginName in savedInactiveList: |
435 savedInactiveList.remove(self.__develPluginName) |
453 savedInactiveList.remove(self.__develPluginName) |
436 names = sorted(self.__inactiveModules.keys()) |
454 names = sorted(self.__inactiveModules.keys()) |
513 Private method to check, if a plugin can be activated. |
534 Private method to check, if a plugin can be activated. |
514 |
535 |
515 @param module reference to the module to be activated |
536 @param module reference to the module to be activated |
516 @return flag indicating, if the module satisfies all requirements |
537 @return flag indicating, if the module satisfies all requirements |
517 for being activated (boolean) |
538 for being activated (boolean) |
|
539 @exception PluginModuleFormatError raised to indicate an invalid |
|
540 plug-in module format |
|
541 @exception PluginClassFormatError raised to indicate an invalid |
|
542 plug-in class format |
518 """ |
543 """ |
519 try: |
544 try: |
520 if not hasattr(module, "version"): |
545 if not hasattr(module, "version"): |
521 raise PluginModuleFormatError(module.eric5PluginModuleName, "version") |
546 raise PluginModuleFormatError( |
|
547 module.eric5PluginModuleName, "version") |
522 if not hasattr(module, "className"): |
548 if not hasattr(module, "className"): |
523 raise PluginModuleFormatError(module.eric5PluginModuleName, "className") |
549 raise PluginModuleFormatError( |
|
550 module.eric5PluginModuleName, "className") |
524 className = getattr(module, "className") |
551 className = getattr(module, "className") |
525 if not hasattr(module, className): |
552 if not hasattr(module, className): |
526 raise PluginModuleFormatError(module.eric5PluginModuleName, className) |
553 raise PluginModuleFormatError( |
|
554 module.eric5PluginModuleName, className) |
527 pluginClass = getattr(module, className) |
555 pluginClass = getattr(module, className) |
528 if not hasattr(pluginClass, "__init__"): |
556 if not hasattr(pluginClass, "__init__"): |
529 raise PluginClassFormatError(module.eric5PluginModuleName, |
557 raise PluginClassFormatError(module.eric5PluginModuleName, |
530 className, "__init__") |
558 className, "__init__") |
531 if not hasattr(pluginClass, "activate"): |
559 if not hasattr(pluginClass, "activate"): |
619 |
648 |
620 def getPluginInfos(self): |
649 def getPluginInfos(self): |
621 """ |
650 """ |
622 Public method to get infos about all loaded plugins. |
651 Public method to get infos about all loaded plugins. |
623 |
652 |
624 @return list of tuples giving module name (string), plugin name (string), |
653 @return list of tuples giving module name (string), plugin name |
625 version (string), autoactivate (boolean), active (boolean), |
654 (string), version (string), autoactivate (boolean), active |
626 short description (string), error flag (boolean) |
655 (boolean), short description (string), error flag (boolean) |
627 """ |
656 """ |
628 infos = [] |
657 infos = [] |
629 |
658 |
630 for name in list(self.__activeModules.keys()): |
659 for name in list(self.__activeModules.keys()): |
631 pname, shortDesc, error, version = \ |
660 pname, shortDesc, error, version = \ |
632 self.__getShortInfo(self.__activeModules[name]) |
661 self.__getShortInfo(self.__activeModules[name]) |
633 infos.append((name, pname, version, True, True, shortDesc, error)) |
662 infos.append((name, pname, version, True, True, shortDesc, error)) |
634 for name in list(self.__inactiveModules.keys()): |
663 for name in list(self.__inactiveModules.keys()): |
635 pname, shortDesc, error, version = \ |
664 pname, shortDesc, error, version = \ |
636 self.__getShortInfo(self.__inactiveModules[name]) |
665 self.__getShortInfo(self.__inactiveModules[name]) |
637 infos.append((name, pname, version, True, False, shortDesc, error)) |
666 infos.append( |
|
667 (name, pname, version, True, False, shortDesc, error)) |
638 for name in list(self.__onDemandActiveModules.keys()): |
668 for name in list(self.__onDemandActiveModules.keys()): |
639 pname, shortDesc, error, version = \ |
669 pname, shortDesc, error, version = \ |
640 self.__getShortInfo(self.__onDemandActiveModules[name]) |
670 self.__getShortInfo(self.__onDemandActiveModules[name]) |
641 infos.append((name, pname, version, False, True, shortDesc, error)) |
671 infos.append( |
|
672 (name, pname, version, False, True, shortDesc, error)) |
642 for name in list(self.__onDemandInactiveModules.keys()): |
673 for name in list(self.__onDemandInactiveModules.keys()): |
643 pname, shortDesc, error, version = \ |
674 pname, shortDesc, error, version = \ |
644 self.__getShortInfo(self.__onDemandInactiveModules[name]) |
675 self.__getShortInfo(self.__onDemandInactiveModules[name]) |
645 infos.append((name, pname, version, False, False, shortDesc, error)) |
676 infos.append( |
|
677 (name, pname, version, False, False, shortDesc, error)) |
646 for name in list(self.__failedModules.keys()): |
678 for name in list(self.__failedModules.keys()): |
647 pname, shortDesc, error, version = \ |
679 pname, shortDesc, error, version = \ |
648 self.__getShortInfo(self.__failedModules[name]) |
680 self.__getShortInfo(self.__failedModules[name]) |
649 infos.append((name, pname, version, False, False, shortDesc, error)) |
681 infos.append( |
|
682 (name, pname, version, False, False, shortDesc, error)) |
650 return infos |
683 return infos |
651 |
684 |
652 def __getShortInfo(self, module): |
685 def __getShortInfo(self, module): |
653 """ |
686 """ |
654 Private method to extract the short info from a module. |
687 Private method to extract the short info from a module. |
695 else: |
728 else: |
696 # should not happen |
729 # should not happen |
697 return None |
730 return None |
698 |
731 |
699 details["moduleName"] = name |
732 details["moduleName"] = name |
700 details["moduleFileName"] = getattr(module, "eric5PluginModuleFilename", "") |
733 details["moduleFileName"] = getattr( |
|
734 module, "eric5PluginModuleFilename", "") |
701 details["pluginName"] = getattr(module, "name", "") |
735 details["pluginName"] = getattr(module, "name", "") |
702 details["version"] = getattr(module, "version", "") |
736 details["version"] = getattr(module, "version", "") |
703 details["author"] = getattr(module, "author", "") |
737 details["author"] = getattr(module, "author", "") |
704 details["description"] = getattr(module, "longDescription", "") |
738 details["description"] = getattr(module, "longDescription", "") |
705 details["autoactivate"] = autoactivate |
739 details["autoactivate"] = autoactivate |
787 """ |
822 """ |
788 Public method to get data to display information about a plugins |
823 Public method to get data to display information about a plugins |
789 external tool. |
824 external tool. |
790 |
825 |
791 @return list of dictionaries containing the data. Each dictionary must |
826 @return list of dictionaries containing the data. Each dictionary must |
792 either contain data for the determination or the data to be displayed.<br /> |
827 either contain data for the determination or the data to be |
|
828 displayed.<br /> |
793 A dictionary of the first form must have the following entries: |
829 A dictionary of the first form must have the following entries: |
794 <ul> |
830 <ul> |
795 <li>programEntry - indicator for this dictionary form (boolean), |
831 <li>programEntry - indicator for this dictionary form |
796 always True</li> |
832 (boolean), always True</li> |
797 <li>header - string to be diplayed as a header (string)</li> |
833 <li>header - string to be diplayed as a header (string)</li> |
798 <li>exe - the executable (string)</li> |
834 <li>exe - the executable (string)</li> |
799 <li>versionCommand - commandline parameter for the exe (string)</li> |
835 <li>versionCommand - commandline parameter for the exe |
800 <li>versionStartsWith - indicator for the output line containing |
836 (string)</li> |
801 the version (string)</li> |
837 <li>versionStartsWith - indicator for the output line |
|
838 containing the version (string)</li> |
802 <li>versionPosition - number of element containing the |
839 <li>versionPosition - number of element containing the |
803 version (integer)</li> |
840 version (integer)</li> |
804 <li>version - version to be used as default (string)</li> |
841 <li>version - version to be used as default (string)</li> |
805 <li>versionCleanup - tuple of two integers giving string positions |
842 <li>versionCleanup - tuple of two integers giving string |
806 start and stop for the version string (tuple of integers)</li> |
843 positions start and stop for the version string |
|
844 (tuple of integers)</li> |
807 </ul> |
845 </ul> |
808 A dictionary of the second form must have the following entries: |
846 A dictionary of the second form must have the following entries: |
809 <ul> |
847 <ul> |
810 <li>programEntry - indicator for this dictionary form (boolean), |
848 <li>programEntry - indicator for this dictionary form |
811 always False</li> |
849 (boolean), always False</li> |
812 <li>header - string to be diplayed as a header (string)</li> |
850 <li>header - string to be diplayed as a header (string)</li> |
813 <li>text - entry text to be shown (string)</li> |
851 <li>text - entry text to be shown (string)</li> |
814 <li>version - version text to be shown (string)</li> |
852 <li>version - version text to be shown (string)</li> |
815 </ul> |
853 </ul> |
816 """ |
854 """ |
831 |
869 |
832 return infos |
870 return infos |
833 |
871 |
834 def getPluginConfigData(self): |
872 def getPluginConfigData(self): |
835 """ |
873 """ |
836 Public method to get the config data of all active, non on-demand plugins |
874 Public method to get the config data of all active, non on-demand |
837 used by the configuration dialog. |
875 plugins used by the configuration dialog. |
838 |
876 |
839 Plugins supporting this functionality must provide the plugin module |
877 Plugins supporting this functionality must provide the plugin module |
840 function 'getConfigData' returning a dictionary with unique keys |
878 function 'getConfigData' returning a dictionary with unique keys |
841 of lists with the following list contents: |
879 of lists with the following list contents: |
842 <dl> |
880 <dl> |
843 <dt>display string</dt> |
881 <dt>display string</dt> |
844 <dd>string shown in the selection area of the configuration page. |
882 <dd>string shown in the selection area of the configuration page. |
845 This should be a localized string</dd> |
883 This should be a localized string</dd> |
846 <dt>pixmap name</dt> |
884 <dt>pixmap name</dt> |
847 <dd>filename of the pixmap to be shown next to the display string</dd> |
885 <dd>filename of the pixmap to be shown next to the display |
|
886 string</dd> |
848 <dt>page creation function</dt> |
887 <dt>page creation function</dt> |
849 <dd>plugin module function to be called to create the configuration |
888 <dd>plugin module function to be called to create the configuration |
850 page. The page must be subclasses from |
889 page. The page must be subclasses from |
851 Preferences.ConfigurationPages.ConfigurationPageBase and must |
890 Preferences.ConfigurationPages.ConfigurationPageBase and must |
852 implement a method called 'save' to save the settings. A parent |
891 implement a method called 'save' to save the settings. A parent |
853 entry will be created in the selection list, if this value is None.</dd> |
892 entry will be created in the selection list, if this value is |
|
893 None.</dd> |
854 <dt>parent key</dt> |
894 <dt>parent key</dt> |
855 <dd>dictionary key of the parent entry or None, if this defines a |
895 <dd>dictionary key of the parent entry or None, if this defines a |
856 toplevel entry.</dd> |
896 toplevel entry.</dd> |
857 <dt>reference to configuration page</dt> |
897 <dt>reference to configuration page</dt> |
858 <dd>This will be used by the configuration dialog and must always be None</dd> |
898 <dd>This will be used by the configuration dialog and must always |
|
899 be None</dd> |
859 </dl> |
900 </dl> |
|
901 |
|
902 @return plug-in configuration data |
860 """ |
903 """ |
861 configData = {} |
904 configData = {} |
862 for module in list(self.__activeModules.values()) + \ |
905 for module in list(self.__activeModules.values()) + \ |
863 list(self.__onDemandActiveModules.values()) + \ |
906 list(self.__onDemandActiveModules.values()) + \ |
864 list(self.__onDemandInactiveModules.values()): |
907 list(self.__onDemandInactiveModules.values()): |
886 @return flag indicating, if the plugin is active (boolean) |
929 @return flag indicating, if the plugin is active (boolean) |
887 """ |
930 """ |
888 return pluginName in self.__activeModules or \ |
931 return pluginName in self.__activeModules or \ |
889 pluginName in self.__onDemandActiveModules |
932 pluginName in self.__onDemandActiveModules |
890 |
933 |
891 ############################################################################ |
934 ########################################################################### |
892 ## Specialized plugin module handling methods below |
935 ## Specialized plugin module handling methods below |
893 ############################################################################ |
936 ########################################################################### |
894 |
937 |
895 ############################################################################ |
938 ########################################################################### |
896 ## VCS related methods below |
939 ## VCS related methods below |
897 ############################################################################ |
940 ########################################################################### |
898 |
941 |
899 def getVcsSystemIndicators(self): |
942 def getVcsSystemIndicators(self): |
900 """ |
943 """ |
901 Public method to get the Vcs System indicators. |
944 Public method to get the Vcs System indicators. |
902 |
945 |
903 Plugins supporting this functionality must support the module function |
946 Plugins supporting this functionality must support the module function |
904 getVcsSystemIndicator returning a dictionary with indicator as key and |
947 getVcsSystemIndicator returning a dictionary with indicator as key and |
905 a tuple with the vcs name (string) and vcs display string (string). |
948 a tuple with the vcs name (string) and vcs display string (string). |
906 |
949 |
907 @return dictionary with indicator as key and a list of tuples as values. |
950 @return dictionary with indicator as key and a list of tuples as |
908 Each tuple contains the vcs name (string) and vcs display string (string). |
951 values. Each tuple contains the vcs name (string) and vcs display |
|
952 string (string). |
909 """ |
953 """ |
910 vcsDict = {} |
954 vcsDict = {} |
911 |
955 |
912 for name, module in \ |
956 for name, module in \ |
913 list(self.__onDemandActiveModules.items()) + \ |
957 list(self.__onDemandActiveModules.items()) + \ |
949 try: |
994 try: |
950 os.mkdir(downloadDir, 0o755) |
995 os.mkdir(downloadDir, 0o755) |
951 except (OSError, IOError) as err: |
996 except (OSError, IOError) as err: |
952 E5MessageBox.critical(self.__ui, |
997 E5MessageBox.critical(self.__ui, |
953 self.trUtf8("Plugin Manager Error"), |
998 self.trUtf8("Plugin Manager Error"), |
954 self.trUtf8("""<p>The plugin download directory <b>{0}</b> """ |
999 self.trUtf8( |
955 """could not be created. Please configure it """ |
1000 """<p>The plugin download directory""" |
956 """via the configuration dialog.</p>""" |
1001 """ <b>{0}</b> could not be created. Please""" |
957 """<p>Reason: {1}</p>""")\ |
1002 """ configure it via the configuration""" |
|
1003 """ dialog.</p><p>Reason: {1}</p>""") |
958 .format(downloadDir, str(err))) |
1004 .format(downloadDir, str(err))) |
959 downloadDir = "" |
1005 downloadDir = "" |
960 |
1006 |
961 Preferences.setPluginManager("DownloadPath", downloadDir) |
1007 Preferences.setPluginManager("DownloadPath", downloadDir) |
962 |
1008 |