PluginPyLint.py

changeset 0
1c1ac27f3cf1
child 3
78fc974034dc
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)

eric ide

mercurial