diff -r 000000000000 -r 1c1ac27f3cf1 PluginPyLint.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PluginPyLint.py Fri Jul 29 19:03:10 2011 +0200 @@ -0,0 +1,507 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2007 - 2011 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the PyLint plug-in. +""" + +import os +import sys +import copy + +from PyQt4.QtCore import QObject, QTranslator, QCoreApplication +from PyQt4.QtGui import QDialog + +from E5Gui.E5Application import e5App +from E5Gui.E5Action import E5Action +from E5Gui import E5MessageBox + +from PyLint.PyLintConfigDialog import PyLintConfigDialog +from PyLint.PyLintExecDialog import PyLintExecDialog + +import Utilities + +# Start-of-Header +name = "PyLint Plugin" +author = "Detlev Offenbach <detlev@die-offenbachs.de>" +autoactivate = True +deactivateable = True +version = "5.0.0" +className = "PyLintPlugin" +packageName = "PyLint" +shortDescription = "Show the PyLint dialogs." +longDescription = """This plug-in implements the PyLint dialogs.""" \ + """ PyLint is used to check Python source files according to various rules.""" +needsRestart = False +pyqtApi = 2 +# End-of-Header + +error = "" + + +def exeDisplayData(): + """ + Public method to support the display of some executable info. + + @return dictionary containing the data to query the presence of + the executable + """ + data = { + "programEntry": True, + "header": QCoreApplication.translate("PyLintPlugin", + "Checkers - Pylint"), + "exe": 'dummypylint', + "versionCommand": '--version', + "versionStartsWith": 'dummypylint', + "versionPosition": -1, + "version": "", + "versionCleanup": (0, -1), + } + exe = _findExecutable() + if exe: + data["exe"] = exe + data["versionStartsWith"] = 'pylint' + + return data + + +def _findExecutable(): + """ + Restricted function to determine the name and path of the executable. + + @return path name of the executable (string) + """ + if Utilities.isWindowsPlatform(): + # + # Windows + # + exe = 'pylint.bat' + if Utilities.isinpath(exe): + return exe + try: + #only since python 3.2 + import sysconfig + scripts = sysconfig.get_path('scripts', 'nt') + return os.path.join(scripts, exe) + except ImportError: + try: + import winreg + except ImportError: + # give up ... + return None + + def getExePath(branch): + version = str(sys.version_info.major) + '.' + \ + str(sys.version_info.minor) + try: + software = winreg.OpenKey(branch, 'Software') + python = winreg.OpenKey(software, 'Python') + pcore = winreg.OpenKey(python, 'PythonCore') + version = winreg.OpenKey(pcore, version) + installpath = winreg.QueryValue(version, 'InstallPath') + return os.path.join(installpath, 'Scripts', exe) + except WindowsError: # __IGNORE_WARNING__ + return None + + exePath = getExePath(winreg.HKEY_CURRENT_USER) + if not exePath: + exePath = getExePath(winreg.HKEY_LOCAL_MACHINE) + return exePath + else: + # + # Linux, Unix ... + pylintScript = 'pylint' + # There could be multiple pylint executables in the path + # e.g. for different python variants + path = Utilities.getEnvironmentEntry('PATH') + # environment variable not defined + if path is None: + return None + + # step 1: determine possible candidates + exes = [] + dirs = path.split(os.pathsep) + for dir in dirs: + exe = os.path.join(dir, pylintScript) + if os.access(exe, os.X_OK): + exes.append(exe) + + # step 2: determine the Python 3 variant + found = False + for exe in exes: + try: + f = open(exe, "r") + line0 = f.readline() + if "python3" in line0.lower(): + found = True + finally: + f.close() + if found: + return exe + + return None + + +def _checkProgram(): + """ + Restricted function to check the availability of pylint. + + @return flag indicating availability (boolean) + """ + global error + + if _findExecutable() is None: + error = QCoreApplication.translate("PyLintPlugin", + "The pylint executable could not be found.") + return False + else: + return True + + +class PyLintPlugin(QObject): + """ + Class implementing the PyLint plug-in. + """ + def __init__(self, ui): + """ + Constructor + + @param ui reference to the user interface object (UI.UserInterface) + """ + QObject.__init__(self, ui) + self.__ui = ui + self.__initialize() + + self.__translator = None + self.__loadTranslator() + + def __initialize(self): + """ + Private slot to (re)initialize the plugin. + """ + self.__projectAct = None + self.__projectShowAct = None + self.__pylintPDialog = None + + self.__projectBrowserAct = None + self.__projectBrowserShowAct = None + self.__projectBrowserMenu = None + self.__pylintPsbDialog = None + + self.__editors = [] + self.__editorAct = None + self.__editorPylintDialog = None + self.__editorParms = None + + def activate(self): + """ + Public method to activate this plugin. + + @return tuple of None and activation status (boolean) + """ + global error + + # pylint is only activated if it is available + if not _checkProgram(): + return None, False + + try: + from pylint.__pkginfo__ import numversion + if numversion < (0, 23, 0): + error = self.trUtf8("PyLint version < 0.23.0.") + return None, False + except ImportError: + error = self.trUtf8("Cannot determine pylint version.") + return None, False + + menu = e5App().getObject("Project").getMenu("Checks") + if menu: + self.__projectAct = E5Action(self.trUtf8('Run PyLint'), + self.trUtf8('Run &PyLint...'), 0, 0, + self, 'project_check_pylint') + self.__projectAct.setStatusTip( + self.trUtf8('Check project, packages or modules with pylint.')) + self.__projectAct.setWhatsThis(self.trUtf8( + """<b>Run PyLint...</b>""" + """<p>This checks the project, packages or modules using pylint.</p>""" + )) + self.__projectAct.triggered[()].connect(self.__projectPylint) + e5App().getObject("Project").addE5Actions([self.__projectAct]) + menu.addAction(self.__projectAct) + + self.__projectShowAct = E5Action(self.trUtf8('Show PyLint Dialog'), + self.trUtf8('Show Py&Lint Dialog...'), 0, 0, + self, 'project_check_pylintshow') + self.__projectShowAct.setStatusTip( + self.trUtf8('Show the PyLint dialog with the results of the last run.')) + self.__projectShowAct.setWhatsThis(self.trUtf8( + """<b>Show PyLint Dialog...</b>""" + """<p>This shows the PyLint dialog with the results""" + """ of the last run.</p>""" + )) + self.__projectShowAct.triggered[()].connect(self.__projectPylintShow) + e5App().getObject("Project").addE5Actions([self.__projectShowAct]) + menu.addAction(self.__projectShowAct) + + self.__editorAct = E5Action(self.trUtf8('Run PyLint'), + self.trUtf8('Run &PyLint...'), 0, 0, + self, "") + self.__editorAct.setWhatsThis(self.trUtf8( + """<b>Run PyLint...</b>""" + """<p>This checks the loaded module using pylint.</p>""" + )) + self.__editorAct.triggered[()].connect(self.__editorPylint) + + e5App().getObject("Project").showMenu.connect(self.__projectShowMenu) + e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\ + .showMenu.connect(self.__projectBrowserShowMenu) + e5App().getObject("ViewManager").editorOpenedEd.connect(self.__editorOpened) + e5App().getObject("ViewManager").editorClosedEd.connect(self.__editorClosed) + + for editor in e5App().getObject("ViewManager").getOpenEditors(): + self.__editorOpened(editor) + + error = "" + return None, True + + def deactivate(self): + """ + Public method to deactivate this plugin. + """ + e5App().getObject("Project").showMenu.disconnect(self.__projectShowMenu) + e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\ + .showMenu.disconnect(self.__projectBrowserShowMenu) + e5App().getObject("ViewManager").editorOpenedEd.disconnect(self.__editorOpened) + e5App().getObject("ViewManager").editorClosedEd.disconnect(self.__editorClosed) + + menu = e5App().getObject("Project").getMenu("Checks") + if menu: + if self.__projectAct: + menu.removeAction(self.__projectAct) + e5App().getObject("Project").removeE5Actions([self.__projectAct]) + if self.__projectShowAct: + menu.removeAction(self.__projectShowAct) + e5App().getObject("Project").removeE5Actions([self.__projectShowAct]) + + if self.__projectBrowserMenu: + if self.__projectBrowserAct: + self.__projectBrowserMenu.removeAction(self.__projectBrowserAct) + if self.__projectBrowserShowAct: + self.__projectBrowserMenu.removeAction(self.__projectBrowserShowAct) + + for editor in self.__editors: + editor.showMenu.disconnect(self.__editorShowMenu) + menu = editor.getMenu("Checks") + if menu is not None: + menu.removeAction(self.__editorAct) + + self.__initialize() + + def __loadTranslator(self): + """ + Private method to load the translation file. + """ + if self.__ui is not None: + loc = self.__ui.getLocale() + if loc and loc != "C": + locale_dir = \ + os.path.join(os.path.dirname(__file__), "PyLint", "i18n") + translation = "pylint_{0}".format(loc) + translator = QTranslator(None) + loaded = translator.load(translation, locale_dir) + if loaded: + self.__translator = translator + e5App().installTranslator(self.__translator) + else: + print("Warning: translation file '{0}' could not be loaded."\ + .format(translation)) + print("Using default.") + + def __projectShowMenu(self, menuName, menu): + """ + Private slot called, when the the project menu or a submenu is + about to be shown. + + @param menuName name of the menu to be shown (string) + @param menu reference to the menu (QMenu) + """ + if menuName == "Checks": + if self.__projectAct is not None: + self.__projectAct.setEnabled( + e5App().getObject("Project").getProjectLanguage() == "Python3") + if self.__projectShowAct is not None: + self.__projectShowAct.setEnabled( + e5App().getObject("Project").getProjectLanguage() == "Python3") + self.__projectShowAct.setEnabled(self.__pylintPDialog is not None) + + def __projectBrowserShowMenu(self, menuName, menu): + """ + Private slot called, when the the project browser menu or a submenu is + about to be shown. + + @param menuName name of the menu to be shown (string) + @param menu reference to the menu (QMenu) + """ + if menuName == "Checks" and \ + e5App().getObject("Project").getProjectLanguage() == "Python3": + self.__projectBrowserMenu = menu + if self.__projectBrowserAct is None: + self.__projectBrowserAct = E5Action(self.trUtf8('Run PyLint'), + self.trUtf8('Run &PyLint...'), 0, 0, + self, '') + self.__projectBrowserAct.setWhatsThis(self.trUtf8( + """<b>Run PyLint...</b>""" + """<p>This checks the project, packages or modules""" + """ using pylint.</p>""" + )) + self.__projectBrowserAct.triggered[()].connect( + self.__projectBrowserPylint) + + if self.__projectBrowserShowAct is None: + self.__projectBrowserShowAct = \ + E5Action(self.trUtf8('Show PyLint Dialog'), + self.trUtf8('Show Py&Lint Dialog...'), 0, 0, + self, '') + self.__projectBrowserShowAct.setWhatsThis(self.trUtf8( + """<b>Show PyLint Dialog...</b>""" + """<p>This shows the PyLint dialog with the results""" + """ of the last run.</p>""" + )) + self.__projectBrowserShowAct.triggered[()].connect( + self.__projectBrowserPylintShow) + + if not self.__projectBrowserAct in menu.actions(): + menu.addAction(self.__projectBrowserAct) + if not self.__projectBrowserShowAct in menu.actions(): + menu.addAction(self.__projectBrowserShowAct) + self.__projectBrowserShowAct.setEnabled(self.__pylintPsbDialog is not None) + + def __pyLint(self, project, mpName, forProject, forEditor=False): + """ + Private method used to perform a PyLint run. + + @param project reference to the Project object + @param mpName name of module or package to be checked (string) + @param forProject flag indicating a run for the project (boolean) + """ + if forEditor: + parms = copy.deepcopy(self.__editorParms) + else: + parms = project.getData('CHECKERSPARMS', "PYLINT") + exe = _findExecutable() + if exe is None: + E5MessageBox.critical(None, + self.trUtf8("pylint"), + self.trUtf8("""The pylint executable could not be found.""")) + return + + dlg = PyLintConfigDialog(project.getProjectPath(), exe, parms) + if dlg.exec_() == QDialog.Accepted: + args, parms = dlg.generateParameters() + self.__editorParms = copy.deepcopy(parms) + if not forEditor: + project.setData('CHECKERSPARMS', "PYLINT", parms) + + # now do the call + dlg2 = PyLintExecDialog() + try: + reportFile = parms['reportFile'] + except KeyError: + reportFile = None + res = dlg2.start(args, mpName, reportFile, project.getProjectPath()) + if res: + dlg2.show() + if forProject: + self.__pylintPDialog = dlg2 + elif forEditor: + self.__editorPylintDialog = dlg2 + else: + self.__pylintPsbDialog = dlg2 + + def __projectPylint(self): + """ + Public slot used to check the project files with Pylint. + """ + project = e5App().getObject("Project") + project.saveAllScripts() + self.__pyLint(project, project.getProjectPath(), True) + + def __projectPylintShow(self): + """ + Public slot to show the PyLint dialog with the results of the last run. + """ + if self.__pylintPDialog is not None: + self.__pylintPDialog.show() + + def __projectBrowserPylint(self): + """ + Private method to handle the Pylint context menu action of the project + sources browser. + """ + project = e5App().getObject("Project") + browser = e5App().getObject("ProjectBrowser").getProjectBrowser("sources") + itm = browser.model().item(browser.currentIndex()) + try: + fn = itm.fileName() + except AttributeError: + fn = itm.dirName() + self.__pyLint(project, fn, False) + + def __projectBrowserPylintShow(self): + """ + Public slot to show the PyLint dialog with the results of the last run. + """ + if self.__pylintPsbDialog is not None: + self.__pylintPsbDialog.show() + + def __editorOpened(self, editor): + """ + Private slot called, when a new editor was opened. + + @param editor reference to the new editor (QScintilla.Editor) + """ + menu = editor.getMenu("Checks") + if menu is not None: + menu.addAction(self.__editorAct) + editor.showMenu.connect(self.__editorShowMenu) + self.__editors.append(editor) + + def __editorClosed(self, editor): + """ + Private slot called, when an editor was closed. + + @param editor reference to the editor (QScintilla.Editor) + """ + try: + self.__editors.remove(editor) + except ValueError: + pass + + def __editorShowMenu(self, menuName, menu, editor): + """ + Private slot called, when the the editor context menu or a submenu is + about to be shown. + + @param menuName name of the menu to be shown (string) + @param menu reference to the menu (QMenu) + @param editor reference to the editor (QScintilla.Editor) + """ + if menuName == "Checks": + if not self.__editorAct in menu.actions(): + menu.addAction(self.__editorAct) + self.__editorAct.setEnabled(editor.isPy3File()) + + def __editorPylint(self): + """ + Private slot to handle the Pylint context menu action of the editors. + """ + editor = e5App().getObject("ViewManager").activeWindow() + if editor is not None: + if not editor.checkDirty(): + return + + fn = editor.getFileName() + project = e5App().getObject("Project") + self.__pyLint(project, fn, False, True)