PluginVulture.py

Sat, 05 Dec 2015 17:14:18 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 05 Dec 2015 17:14:18 +0100
changeset 37
d6409decd959
parent 31
be56e2ed1ebf
child 39
445ed09016ce
permissions
-rw-r--r--

First stable release.

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

# Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the vulture plug-in.
"""

from __future__ import unicode_literals

import os

from PyQt5.QtCore import pyqtSignal, QObject, QTranslator

from E5Gui.E5Application import e5App
from E5Gui.E5Action import E5Action

import Preferences
from Utilities import determinePythonVersion

# Start-Of-Header
name = "Unused Code Checker Plug-in"
author = "Detlev Offenbach <detlev@die-offenbachs.de>"
autoactivate = True
deactivateable = True
version = "1.0.0"
className = "VulturePlugin"
packageName = "VultureChecker"
shortDescription = "Plug-in to detect unused code using the vulture library"
longDescription = (
    """Plug-in to detect unused code using the vulture library."""
)
needsRestart = False
pyqtApi = 2
python2Compatible = True
# End-Of-Header

error = ""
    

class VulturePlugin(QObject):
    """
    Class documentation goes here.
    
    @signal analysisDone(str, dict) emitted when the code analysis has
        been completed for a file
    @signal error(str, str) emitted in case of an error
    @signal batchFinished() emitted when a style check batch is done
    """
    analysisDone = pyqtSignal(str, dict)
    batchFinished = pyqtSignal()
    error = pyqtSignal(str, str)
    
    def __init__(self, ui):
        """
        Constructor
        
        @param ui reference to the user interface object (UI.UserInterface)
        """
        super(VulturePlugin, self).__init__(ui)
        self.__ui = ui
        self.__initialize()
        
        self.backgroundService = e5App().getObject("BackgroundService")
        
        path = os.path.join(os.path.dirname(__file__), packageName)
        try:
            self.backgroundService.serviceConnect(
                'vulture', 'Python2', path, 'VultureCheckerService',
                self.vultureCheckDone,
                onErrorCallback=self.serviceErrorPy2,
                onBatchDone=self.batchJobDone)
            self.backgroundService.serviceConnect(
                'vulture', 'Python3', path, 'VultureCheckerService',
                self.vultureCheckDone,
                onErrorCallback=self.serviceErrorPy3,
                onBatchDone=self.batchJobDone)
            self.hasBatch = True
        except TypeError:
            # backward compatibility for eric 6.0
            self.backgroundService.serviceConnect(
                'vulture', 'Python2', path, 'VultureCheckerService',
                self.vultureCheckDone,
                onErrorCallback=self.serviceErrorPy2)
            self.backgroundService.serviceConnect(
                'vulture', 'Python3', path, 'VultureCheckerService',
                self.vultureCheckDone,
                onErrorCallback=self.serviceErrorPy3)
            self.hasBatch = False
        
        self.queuedBatches = []
        self.batchesFinished = True
        
        self.__translator = None
        self.__loadTranslator()
    
    def __serviceError(self, fn, msg):
        """
        Private slot handling service errors.
        
        @param fn file name
        @type str
        @param msg message text
        @type str
        """
        self.error.emit(fn, msg)
    
    def serviceErrorPy2(self, fx, lang, fn, msg):
        """
        Public slot handling service errors for Python 2.
        
        @param fx service name
        @type str
        @param lang language
        @type str
        @param fn file name
        @type str
        @param msg message text
        @type str
        """
        if fx in ['vulture', 'batch_vulture'] and \
                lang == 'Python2':
            if fx == 'vulture':
                self.__serviceError(fn, msg)
            else:
                self.__serviceError(self.tr("Python 2 batch job"), msg)
                self.batchJobDone(fx, lang)
    
    def serviceErrorPy3(self, fx, lang, fn, msg):
        """
        Public slot handling service errors for Python 3.
        
        @param fx service name
        @type str
        @param lang language
        @type str
        @param fn file name
        @type str
        @param msg message text
        @type str
        """
        if fx in ['vulture', 'batch_vulture'] and \
                lang == 'Python3':
            if fx == 'vulture':
                self.__serviceError(fn, msg)
            else:
                self.__serviceError(self.tr("Python 3 batch job"), msg)
                self.batchJobDone(fx, lang)
    
    def batchJobDone(self, fx, lang):
        """
        Public slot handling the completion of a batch job.
        
        @param fx service name
        @type str
        @param lang language
        @type str
        """
        if fx in ['vulture', 'batch_vulture']:
            if lang in self.queuedBatches:
                self.queuedBatches.remove(lang)
            # prevent sending the signal multiple times
            if len(self.queuedBatches) == 0 and not self.batchesFinished:
                self.batchFinished.emit()
                self.batchesFinished = True
    
    def vultureCheckDone(self, filename, result):
        """
        Public slot to dispatch the result.
        
        @param filename name of the file the results belong to
        @type str
        @param result result dictionary
        @type dict
        """
        self.analysisDone.emit(filename, result)
    
    def __initialize(self):
        """
        Private slot to (re)initialize the plug-in.
        """
        self.__projectAct = None
        self.__projectVultureCheckerDialog = None

    def vultureCheck(self, lang, filename, source):
        """
        Public method to prepare a vulture check for a Python project.

        @param lang language of the files or None to determine by internal
            algorithm
        @type str or None
        @param filename name of the file to analyze
        @type str
        @param source string containing the code
        @type str
        """
        if lang is None:
            lang = 'Python{0}'.format(determinePythonVersion(filename, source))
        if lang not in ['Python2', 'Python3']:
            return
        
        self.backgroundService.enqueueRequest(
            'vulture', lang, filename, [source])

    def vultureCheckBatch(self, argumentsList):
        """
        Public method to prepare a vulture check for a Python project using
        the batch mode.
        
        @param argumentsList list of arguments tuples with each tuple
            containing filename and source
        @type (str, str)
        """
        data = {
            "Python2": [],
            "Python3": [],
        }
        for filename, source in argumentsList:
            lang = 'Python{0}'.format(determinePythonVersion(filename, source))
            if lang not in ['Python2', 'Python3']:
                continue
            else:
                data[lang].append((filename, source))
        
        self.queuedBatches = []
        for lang in ['Python2', 'Python3']:
            if data[lang]:
                self.queuedBatches.append(lang)
                self.backgroundService.enqueueRequest('batch_vulture', lang,
                                                      "", data[lang])
                self.batchesFinished = False
    
    def cancelVultureCheckBatch(self):
        """
        Public method to cancel all batch jobs.
        """
        for lang in ['Python2', 'Python3']:
            self.backgroundService.requestCancel('batch_vulture', lang)
    
    def activate(self):
        """
        Public method to activate this plug-in.
        
        @return tuple of None and activation status (boolean)
        """
        global error
        error = ""     # clear previous error
        
        menu = e5App().getObject("Project").getMenu("Checks")
        if menu:
            self.__projectAct = E5Action(
                self.tr('Check Unused Code'),
                self.tr('&Unused Code...'), 0, 0,
                self, 'project_check_vulture')
            self.__projectAct.setStatusTip(
                self.tr('Check for unused code'))
            self.__projectAct.setWhatsThis(self.tr(
                """<b>Check Unused Code...</b>"""
                """<p>This checks a Python project for unused code.</p>"""
            ))
            self.__projectAct.triggered.connect(
                self.__projectVultureCheck)
            e5App().getObject("Project").addE5Actions([self.__projectAct])
            menu.addAction(self.__projectAct)
        
        e5App().getObject("Project").showMenu.connect(self.__projectShowMenu)
        e5App().getObject("Project").projectClosed.connect(
            self.__projectClosed)
        
        return None, True
    
    def deactivate(self):
        """
        Public method to deactivate this plug-in.
        """
        e5App().getObject("Project").showMenu.disconnect(
            self.__projectShowMenu)
        e5App().getObject("Project").projectClosed.disconnect(
            self.__projectClosed)
        
        menu = e5App().getObject("Project").getMenu("Checks")
        if menu:
            if self.__projectAct is not None:
                menu.removeAction(self.__projectAct)
                e5App().getObject("Project").removeE5Actions(
                    [self.__projectAct])
        
        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__), "VultureChecker", "i18n")
                translation = "vulture_{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
        @type str
        @param menu reference to the menu
        @type QMenu
        """
        if menuName == "Check":
            if self.__projectAct is not None:
                self.__projectAct.setEnabled(
                    e5App().getObject("Project").getProjectLanguage() in
                    ["Python3", "Python2", "Python"])
    
    def __projectVultureCheck(self):
        """
        Private slot used to check the project for unused code.
        """
        project = e5App().getObject("Project")
        project.saveAllScripts()
        ppath = project.getProjectPath()
        files = [os.path.join(ppath, file_)
                 for file_ in project.pdata["SOURCES"]
                 if file_.endswith(
                     tuple(Preferences.getPython("Python3Extensions")) +
                     tuple(Preferences.getPython("PythonExtensions")))]
        
        if self.__projectVultureCheckerDialog is None:
            from VultureChecker.VultureCheckerDialog import \
                VultureCheckerDialog
            self.__projectVultureCheckerDialog = VultureCheckerDialog(self)
        self.__projectVultureCheckerDialog.show()
        self.__projectVultureCheckerDialog.prepare(files, project)
    
    def __projectClosed(self):
        """
        Private slot to handle closing a project.
        """
        self.__projectVultureCheckerDialog and \
            self.__projectVultureCheckerDialog.clear()

eric ide

mercurial