PluginPyLint.py

Sun, 14 Apr 2013 14:45:51 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 14 Apr 2013 14:45:51 +0200
changeset 20
8ca799e4b7e4
parent 18
ae62d56e345d
child 22
68beeb22dade
permissions
-rw-r--r--

Changed usage of QMessageBox and QFileDialog to the eric5 equivalents.

# -*- coding: utf-8 -*-

# Copyright (c) 2007 - 2013 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

import Utilities

# Start-of-Header
name = "PyLint Plugin"
author = "Detlev Offenbach <detlev@die-offenbachs.de>"
autoactivate = True
deactivateable = True
version = "5.2.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
# 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
        
        from PyLint.PyLintConfigDialog import PyLintConfigDialog
        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
            from PyLint.PyLintExecDialog import PyLintExecDialog
            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