Wed, 07 Nov 2012 16:46:31 +0100
Adjusted list of available checkers to pylint 0.23 and newer.
# -*- coding: utf-8 -*- # Copyright (c) 2007 - 2012 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.1.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' scriptSuffixes = ["", "-python{0}".format(sys.version[:1]), "-python{0}".format(sys.version[:3])] # 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: for suffix in scriptSuffixes: exe = os.path.join(dir, pylintScript + suffix) if os.access(exe, os.X_OK): exes.append(exe) # step 2: determine the Python 3 variant found = False if Utilities.isMacPlatform(): checkStr = "Python.framework/Versions/3".lower() else: checkStr = "python3" for exe in exes: try: f = open(exe, "r") line0 = f.readline() if checkStr 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)