Sat, 21 Sep 2019 20:30:56 +0200
Continued to resolve code style issue M841.
# -*- coding: utf-8 -*- # Copyright (c) 2011 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the code style checker plug-in. """ import os from PyQt5.QtCore import QObject, pyqtSignal, QCoreApplication from E5Gui.E5Application import e5App from E5Gui.E5Action import E5Action from Project.ProjectBrowserModel import ProjectBrowserFileItem from Utilities import determinePythonVersion import Preferences import UI.Info # Start-Of-Header name = "Code Style Checker Plugin" author = "Detlev Offenbach <detlev@die-offenbachs.de>" autoactivate = True deactivateable = True version = UI.Info.VersionOnly className = "CodeStyleCheckerPlugin" packageName = "__core__" shortDescription = "Show the Python Code Style Checker dialog." longDescription = ( """This plugin implements the Python Code Style""" """ Checker dialog. A PEP-8 checker is used to check Python source""" """ files for compliance to the code style conventions given in PEP-8.""" """ A PEP-257 checker is used to check Python source files for""" """ compliance to docstring conventions given in PEP-257 and an""" """ eric6 variant is used to check against eric conventions.""" ) pyqtApi = 2 # End-Of-Header error = "" class CodeStyleCheckerPlugin(QObject): """ Class implementing the code style checker plug-in. @signal styleChecked(str, dict, int, list) emitted when the style check was done for a file. @signal batchFinished() emitted when a style check batch is done @signal error(str, str) emitted in case of an error """ styleChecked = pyqtSignal(str, dict, int, list) batchFinished = pyqtSignal() error = pyqtSignal(str, str) def __init__(self, ui): """ Constructor @param ui reference to the user interface object (UI.UserInterface) """ super(CodeStyleCheckerPlugin, self).__init__(ui) self.__ui = ui self.__initialize() self.backgroundService = e5App().getObject("BackgroundService") path = os.path.join( os.path.dirname(__file__), 'CheckerPlugins', 'CodeStyleChecker') self.backgroundService.serviceConnect( 'style', 'Python2', path, 'CodeStyleChecker', self.__translateStyleCheck, onErrorCallback=self.serviceErrorPy2, onBatchDone=self.batchJobDone) self.backgroundService.serviceConnect( 'style', 'Python3', path, 'CodeStyleChecker', self.__translateStyleCheck, onErrorCallback=self.serviceErrorPy3, onBatchDone=self.batchJobDone) self.queuedBatches = [] self.batchesFinished = True def __serviceError(self, fn, msg): """ Private slot handling service errors. @param fn file name (string) @param msg message text (string) """ self.error.emit(fn, msg) def serviceErrorPy2(self, fx, lang, fn, msg): """ Public slot handling service errors for Python 2. @param fx service name (string) @param lang language (string) @param fn file name (string) @param msg message text (string) """ if fx in ['style', 'batch_style'] and lang == 'Python2': if fx == 'style': self.__serviceError(fn, msg) else: self.__serviceError(self.tr("Python 2 batch check"), msg) self.batchJobDone(fx, lang) def serviceErrorPy3(self, fx, lang, fn, msg): """ Public slot handling service errors for Python 2. @param fx service name (string) @param lang language (string) @param fn file name (string) @param msg message text (string) """ if fx in ['style', 'batch_style'] and lang == 'Python3': if fx == 'style': self.__serviceError(fn, msg) else: self.__serviceError(self.tr("Python 3 batch check"), msg) self.batchJobDone(fx, lang) def batchJobDone(self, fx, lang): """ Public slot handling the completion of a batch job. @param fx service name (string) @param lang language (string) """ if fx in ['style', 'batch_style']: 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 __initialize(self): """ Private slot to (re)initialize the plugin. """ self.__projectAct = None self.__projectCodeStyleCheckerDialog = None self.__projectBrowserAct = None self.__projectBrowserMenu = None self.__projectBrowserCodeStyleCheckerDialog = None self.__editors = [] self.__editorAct = None self.__editorCodeStyleCheckerDialog = None def styleCheck(self, lang, filename, source, args): """ Public method to prepare a style check on one Python source file. @param lang language of the file or None to determine by internal algorithm @type str or None @param filename source filename @type str @param source string containing the code to check @type str @param args arguments used by the codeStyleCheck function (list of excludeMessages, includeMessages, repeatMessages, fixCodes, noFixCodes, fixIssues, maxLineLength, blankLines, hangClosing, docType, codeComplexityArgs, miscellaneousArgs, errors, eol, encoding, backup) @type list of (str, str, bool, str, str, bool, int, list of (int, int), bool, str, dict, dict, list of str, str, str, bool) """ if lang is None: lang = 'Python{0}'.format(determinePythonVersion(filename, source)) if lang not in ['Python2', 'Python3']: return data = [source, args] self.backgroundService.enqueueRequest('style', lang, filename, data) def styleBatchCheck(self, argumentsList): """ Public method to prepare a style check on multiple Python source files. @param argumentsList list of arguments tuples with each tuple containing filename, source and args as given in styleCheck() method @type list of tuple of (str, str, list) """ data = { "Python2": [], "Python3": [], } for filename, source, args in argumentsList: lang = 'Python{0}'.format(determinePythonVersion(filename, source)) if lang not in ['Python2', 'Python3']: continue else: data[lang].append((filename, source, args)) self.queuedBatches = [] for lang in ['Python2', 'Python3']: if data[lang]: self.queuedBatches.append(lang) self.backgroundService.enqueueRequest('batch_style', lang, "", data[lang]) self.batchesFinished = False def cancelStyleBatchCheck(self): """ Public method to cancel all batch jobs. """ for lang in ['Python2', 'Python3']: self.backgroundService.requestCancel('batch_style', lang) def __translateStyleCheck(self, fn, codeStyleCheckerStats, results): """ Private slot called after perfoming a style check on one file. @param fn filename of the just checked file (str) @param codeStyleCheckerStats stats of style and name check (dict) @param results tuple for each found violation of style (tuple of lineno (int), position (int), text (str), fixed (bool), autofixing (bool), fixedMsg (str)) """ from CheckerPlugins.CodeStyleChecker.translations import ( getTranslatedMessage ) fixes = 0 for result in results: msg = getTranslatedMessage(result[2]) fixedMsg = result.pop() if fixedMsg: fixes += 1 trFixedMsg = getTranslatedMessage(fixedMsg) msg += "\n" + QCoreApplication.translate( 'CodeStyleCheckerDialog', "Fix: {0}").format(trFixedMsg) result[2] = msg self.styleChecked.emit(fn, codeStyleCheckerStats, fixes, results) def activate(self): """ Public method to activate this plugin. @return tuple of None and activation status (boolean) """ menu = e5App().getObject("Project").getMenu("Checks") if menu: self.__projectAct = E5Action( self.tr('Check Code Style'), self.tr('&Code Style...'), 0, 0, self, 'project_check_pep8') self.__projectAct.setStatusTip( self.tr('Check code style.')) self.__projectAct.setWhatsThis(self.tr( """<b>Check Code Style...</b>""" """<p>This checks Python files for compliance to the""" """ code style conventions given in various PEPs.</p>""" )) self.__projectAct.triggered.connect( self.__projectCodeStyleCheck) e5App().getObject("Project").addE5Actions([self.__projectAct]) menu.addAction(self.__projectAct) self.__editorAct = E5Action( self.tr('Check Code Style'), self.tr('&Code Style...'), 0, 0, self, "") self.__editorAct.setWhatsThis(self.tr( """<b>Check Code Style...</b>""" """<p>This checks Python files for compliance to the""" """ code style conventions given in various PEPs.</p>""" )) self.__editorAct.triggered.connect(self.__editorCodeStyleCheck) 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) 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: menu.removeAction(self.__projectAct) if self.__projectBrowserMenu: if self.__projectBrowserAct: self.__projectBrowserMenu.removeAction( self.__projectBrowserAct) 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 __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" and self.__projectAct is not None: self.__projectAct.setEnabled( e5App().getObject("Project").getProjectLanguage() in ["Python3", "Python2", "Python", "MicroPython"]) 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() in ["Python3", "Python2", "Python", "MicroPython"] ): self.__projectBrowserMenu = menu if self.__projectBrowserAct is None: self.__projectBrowserAct = E5Action( self.tr('Check Code Style'), self.tr('&Code Style...'), 0, 0, self, "") self.__projectBrowserAct.setWhatsThis(self.tr( """<b>Check Code Style...</b>""" """<p>This checks Python files for compliance to the""" """ code style conventions given in various PEPs.</p>""" )) self.__projectBrowserAct.triggered.connect( self.__projectBrowserCodeStyleCheck) if self.__projectBrowserAct not in menu.actions(): menu.addAction(self.__projectBrowserAct) def __projectCodeStyleCheck(self): """ Private slot used to check the project files for code style. """ 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")))] from CheckerPlugins.CodeStyleChecker import CodeStyleCheckerDialog self.__projectCodeStyleCheckerDialog = ( CodeStyleCheckerDialog.CodeStyleCheckerDialog(self) ) self.__projectCodeStyleCheckerDialog.show() self.__projectCodeStyleCheckerDialog.prepare(files, project) def __projectBrowserCodeStyleCheck(self): """ Private method to handle the code style check context menu action of the project sources browser. """ browser = ( e5App().getObject("ProjectBrowser").getProjectBrowser("sources") ) if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1: fn = [] for itm in browser.getSelectedItems([ProjectBrowserFileItem]): fn.append(itm.fileName()) isDir = False else: itm = browser.model().item(browser.currentIndex()) try: fn = itm.fileName() isDir = False except AttributeError: fn = itm.dirName() isDir = True from CheckerPlugins.CodeStyleChecker import CodeStyleCheckerDialog self.__projectBrowserCodeStyleCheckerDialog = ( CodeStyleCheckerDialog.CodeStyleCheckerDialog(self) ) self.__projectBrowserCodeStyleCheckerDialog.show() if isDir: self.__projectBrowserCodeStyleCheckerDialog.start( fn, save=True) else: self.__projectBrowserCodeStyleCheckerDialog.start( fn, save=True, repeat=True) 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 """ if menuName == "Checks": if self.__editorAct not in menu.actions(): menu.addAction(self.__editorAct) self.__editorAct.setEnabled(editor.isPyFile()) def __editorCodeStyleCheck(self): """ Private slot to handle the code style check context menu action of the editors. """ editor = e5App().getObject("ViewManager").activeWindow() if editor is not None: if editor.checkDirty() and editor.getFileName() is not None: from CheckerPlugins.CodeStyleChecker import ( CodeStyleCheckerDialog ) self.__editorCodeStyleCheckerDialog = ( CodeStyleCheckerDialog.CodeStyleCheckerDialog(self) ) self.__editorCodeStyleCheckerDialog.show() self.__editorCodeStyleCheckerDialog.start( editor.getFileName(), save=True, repeat=True)