Wed, 30 Apr 2014 19:58:17 +0200
Fixed an issue reporting the pylint versions installed.
# -*- coding: utf-8 -*- # Copyright (c) 2007 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the PyLint plug-in. """ from __future__ import unicode_literals try: str = unicode except NameError: pass import re import os import copy import platform from PyQt4.QtCore import QObject, QTranslator, QCoreApplication, QProcess from PyQt4.QtGui import QDialog try: from E5Gui.E5Application import e5App from E5Gui.E5Action import E5Action from E5Gui import E5MessageBox error = "" except ImportError: error = QCoreApplication.translate( "PyLintPlugin", """Your version of Eric5 is not supported.""" """ At least version 5.1.0 of Eric5 is needed.""") from Project.ProjectBrowserModel import ProjectBrowserFileItem import Preferences import Utilities # Start-of-Header name = "PyLint Plugin" author = "Detlev Offenbach <detlev@die-offenbachs.de>" autoactivate = True deactivateable = True version = "5.4.1" 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 python2Compatible = True # End-of-Header exePy2 = [] exePy3 = [] def exeDisplayDataList(): """ Public method to support the display of some executable info. @return dictionary containing the data to query the presence of the executable """ dataList = [] data = { "programEntry": True, "header": QCoreApplication.translate( "PyLintPlugin", "Checkers - Pylint"), "exe": 'dummypylint', "versionCommand": '--version', "versionStartsWith": 'dummypylint', "versionPosition": -1, "version": "", "versionCleanup": (0, -1), } if _checkProgram(): for exePath in (exePy2[0], exePy3[0]): if exePath: data["exe"] = exePath data["versionStartsWith"] = "pylint" dataList.append(data.copy()) else: dataList.append(data) return dataList def __getProgramVersion(exe): """ Private method to generate a program entry. @param exe name of the executable program (string) @return version string of detected version (string) """ proc = QProcess() proc.setProcessChannelMode(QProcess.MergedChannels) proc.start(exe, ['--version']) finished = proc.waitForFinished(10000) if finished: output = str(proc.readAllStandardOutput(), Preferences.getSystem("IOEncoding"), 'replace') versionRe = re.compile('^pylint', re.UNICODE) for line in output.splitlines(): if versionRe.search(line): version = line.split()[-1] version = version[:-1] break else: version = '0.0.0' return version def _findExecutable(majorVersion): """ Restricted function to determine the name and path of the executable. @param majorVersion major python version of the executables (int) @return path name of the executable (string) """ # Determine Python Version if majorVersion == 3: minorVersions = range(5) elif majorVersion == 2: minorVersions = range(5, 9) else: return [] executables = set() if Utilities.isWindowsPlatform(): # # Windows # try: import winreg except ImportError: import _winreg as winreg # __IGNORE_WARNING__ def getExePath(branch, access, versionStr): try: software = winreg.OpenKey(branch, 'Software', 0, access) python = winreg.OpenKey(software, 'Python', 0, access) pcore = winreg.OpenKey(python, 'PythonCore', 0, access) version = winreg.OpenKey(pcore, versionStr, 0, access) installpath = winreg.QueryValue(version, 'InstallPath') exe = os.path.join(installpath, 'Scripts', 'pylint.bat') if os.access(exe, os.X_OK): return exe except WindowsError: # __IGNORE_WARNING__ return None return None for minorVersion in minorVersions: versionStr = '{0}.{1}'.format(majorVersion, minorVersion) exePath = getExePath( winreg.HKEY_CURRENT_USER, winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr) if exePath is not None: executables.add(exePath) exePath = getExePath( winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr) # Even on Intel 64-bit machines it's 'AMD64' if platform.machine() == 'AMD64': if exePath is not None: executables.add(exePath) exePath = getExePath( winreg.HKEY_CURRENT_USER, winreg.KEY_WOW64_64KEY | winreg.KEY_READ, versionStr) if exePath is not None: executables.add(exePath) exePath = getExePath( winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_64KEY | winreg.KEY_READ, versionStr) if exePath is not None: executables.add(exePath) else: # # Linux, Unix ... pylintScript = 'pylint' scriptSuffixes = ["", "-python{0}".format(majorVersion)] for minorVersion in minorVersions: scriptSuffixes.append( "-python{0}.{1}".format(majorVersion, minorVersion)) # 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 [] # 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 variant if Utilities.isMacPlatform(): checkStrings = ["Python.framework/Versions/3".lower(), "python3"] else: checkStrings = ["python3"] _exePy2 = set() _exePy3 = set() for exe in exes: try: f = open(exe, "r") line0 = f.readline() for checkStr in checkStrings: if checkStr in line0.lower(): _exePy3.add(exe) break else: _exePy2.add(exe) finally: f.close() executables = _exePy3 if majorVersion == 3 else _exePy2 # Find the executable with the highest version number maxVersion = '0.0.0' maxExe = '' for executable in list(executables): version = __getProgramVersion(executable) if version > maxVersion: maxVersion = version maxExe = executable return maxExe, maxVersion def _checkProgram(): """ Restricted function to check the availability of pylint. @return flag indicating availability (boolean) """ global error, exePy2, exePy3 exePy2 = _findExecutable(2) exePy3 = _findExecutable(3) if exePy2[0] == '' and exePy3[0] == '': error = QCoreApplication.translate( "PyLintPlugin", "The pylint executable could not be found.") return False elif exePy2[1] < '0.23.0' and exePy3[1] < '0.23.0': error = QCoreApplication.translate( "PyLintPlugin", "PyLint version < 0.23.0.") 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 # There is already an error, don't activate if error: return None, False # pylint is only activated if it is available if not _checkProgram(): 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": lang = e5App().getObject("Project").getProjectLanguage() if self.__projectAct is not None: self.__projectAct.setEnabled(lang.startswith("Python")) if self.__projectShowAct is not None: self.__projectShowAct.setEnabled(lang.startswith("Python")) 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()\ .startswith("Python"): 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) enable = e5App().getObject("ProjectBrowser")\ .getProjectBrowser("sources")\ .getSelectedItemsCount([ProjectBrowserFileItem]) == 1 self.__projectBrowserAct.setEnabled(enable) self.__projectBrowserShowAct.setEnabled( enable and 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) @param forEditor flag indicating a run for an editor (boolean) """ if forEditor: parms = copy.deepcopy(self.__editorParms) editor = e5App().getObject("ViewManager").getOpenEditor(mpName) majorVersionStr = editor.getLanguage() else: parms = project.getData('CHECKERSPARMS', "PYLINT") majorVersionStr = project.getProjectLanguage() exe, version = {"Python": exePy2, "Python2": exePy2, "Python3": exePy3}.get(majorVersionStr) if exe == '': E5MessageBox.critical( None, self.trUtf8("pylint"), self.trUtf8("""The pylint executable could not be found.""")) return elif version < '0.23.0': E5MessageBox.critical( None, self.trUtf8("pylint"), self.trUtf8("PyLint version < 0.23.0.")) return from PyLint.PyLintConfigDialog import PyLintConfigDialog dlg = PyLintConfigDialog(project.getProjectPath(), exe, parms, version) 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 from PyLint.PyLintExecDialog import PyLintExecDialog dlg2 = PyLintExecDialog() reportFile = parms.get('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() or editor.isPy2File()) 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)