Sun, 19 May 2024 17:20:16 +0200
Relocated the Version information into a top level '__version__.py' module.
# -*- coding: utf-8 -*- # Copyright (c) 2011 - 2024 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the code style checker plug-in. """ import contextlib import os import textwrap from PyQt6.QtCore import QCoreApplication, QObject, pyqtSignal from eric7.__version__ import VersionOnly from eric7 import Preferences from eric7.EricGui.EricAction import EricAction from eric7.EricWidgets.EricApplication import ericApp from eric7.Project.ProjectBrowserModel import ProjectBrowserFileItem from eric7.SystemUtilities import PythonUtilities # Start-Of-Header __header__ = { "name": "Code Style Checker Plugin", "author": "Detlev Offenbach <detlev@die-offenbachs.de>", "autoactivate": True, "deactivateable": True, "version": 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""" """ eric variant is used to check against eric conventions.""" ), "pyqtApi": 2, } # End-Of-Header error = "" # noqa: U200 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 @type UserInterface """ super().__init__(ui) self.__ui = ui self.__initialize() self.backgroundService = ericApp().getObject("BackgroundService") path = os.path.join( os.path.dirname(__file__), "CheckerPlugins", "CodeStyleChecker" ) self.backgroundService.serviceConnect( "style", "Python3", path, "CodeStyleChecker", self.__translateStyleCheck, onErrorCallback=self.serviceErrorPy3, onBatchDone=self.batchJobDone, ) self.queuedBatches = [] self.batchesFinished = True self.__wrapper = textwrap.TextWrapper(width=80) 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 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 ["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 @type str @param lang language @type str """ 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 list of code lines to be checked @type list of 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( PythonUtilities.determinePythonVersion(filename, source) ) if lang != "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 = { "Python3": [], } for filename, source, args in argumentsList: lang = "Python{0}".format( PythonUtilities.determinePythonVersion(filename, source) ) if lang != "Python3": continue else: data[lang].append((filename, source, args)) self.queuedBatches = [] if data["Python3"]: self.queuedBatches.append("Python3") self.backgroundService.enqueueRequest( "batch_style", "Python3", "", data["Python3"] ) self.batchesFinished = False def cancelStyleBatchCheck(self): """ Public method to cancel all batch jobs. """ self.backgroundService.requestCancel("batch_style", "Python3") def __translateStyleCheck(self, fn, codeStyleCheckerStats, results): """ Private slot called after performing a style check on one file. @param fn filename of the just checked file @type str @param codeStyleCheckerStats stats of style and name check @type dict @param results dictionary containing the check result data (see CodesStyleChecker.__checkCodeStyle for details) @type dict """ from eric7.Plugins.CheckerPlugins.CodeStyleChecker.translations import ( getTranslatedMessage, ) fixes = 0 for result in results: msg = getTranslatedMessage(result["code"], result["args"]) if result["fixcode"]: fixes += 1 trFixedMsg = getTranslatedMessage(result["fixcode"], result["fixargs"]) msg += "\n" + QCoreApplication.translate( "CodeStyleCheckerDialog", "Fix: {0}" ).format(trFixedMsg) result["display"] = "\n".join(self.__wrapper.wrap(msg)) self.styleChecked.emit(fn, codeStyleCheckerStats, fixes, results) def activate(self): """ Public method to activate this plugin. @return tuple of None and activation status @rtype bool """ menu = ericApp().getObject("Project").getMenu("Checks") if menu: self.__projectAct = EricAction( 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) ericApp().getObject("Project").addEricActions([self.__projectAct]) menu.addAction(self.__projectAct) self.__editorAct = EricAction( 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) ericApp().getObject("Project").showMenu.connect(self.__projectShowMenu) ericApp().getObject("ProjectBrowser").getProjectBrowser( "sources" ).showMenu.connect(self.__projectBrowserShowMenu) ericApp().getObject("ViewManager").editorOpenedEd.connect(self.__editorOpened) ericApp().getObject("ViewManager").editorClosedEd.connect(self.__editorClosed) for editor in ericApp().getObject("ViewManager").getOpenEditors(): self.__editorOpened(editor) return None, True def deactivate(self): """ Public method to deactivate this plugin. """ ericApp().getObject("Project").showMenu.disconnect(self.__projectShowMenu) ericApp().getObject("ProjectBrowser").getProjectBrowser( "sources" ).showMenu.disconnect(self.__projectBrowserShowMenu) ericApp().getObject("ViewManager").editorOpenedEd.disconnect( self.__editorOpened ) ericApp().getObject("ViewManager").editorClosedEd.disconnect( self.__editorClosed ) menu = ericApp().getObject("Project").getMenu("Checks") if menu: menu.removeAction(self.__projectAct) if self.__projectBrowserMenu and 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 @type str @param _menu reference to the menu (unused) @type QMenu """ if menuName == "Checks" and self.__projectAct is not None: self.__projectAct.setEnabled( ericApp().getObject("Project").getProjectLanguage() in ["Python3", "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 @type str @param menu reference to the menu @type QMenu """ if menuName == "Checks" and ericApp().getObject( "Project" ).getProjectLanguage() in ["Python3", "MicroPython"]: self.__projectBrowserMenu = menu if self.__projectBrowserAct is None: self.__projectBrowserAct = EricAction( 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. """ from eric7.Plugins.CheckerPlugins.CodeStyleChecker import CodeStyleCheckerDialog project = ericApp().getObject("Project") project.saveAllScripts() ppath = project.getProjectPath() files = [ os.path.join(ppath, file) for file in project.getProjectData(dataKey="SOURCES") if file.endswith(tuple(Preferences.getPython("Python3Extensions"))) ] 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. """ from eric7.Plugins.CheckerPlugins.CodeStyleChecker import CodeStyleCheckerDialog browser = ericApp().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 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 @type 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 @type Editor """ with contextlib.suppress(ValueError): self.__editors.remove(editor) 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 @type str @param menu reference to the menu @type QMenu @param editor reference to the editor @type 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. """ from eric7.Plugins.CheckerPlugins.CodeStyleChecker import CodeStyleCheckerDialog editor = ericApp().getObject("ViewManager").activeWindow() if ( editor is not None and editor.checkDirty() and editor.getFileName() is not None ): self.__editorCodeStyleCheckerDialog = ( CodeStyleCheckerDialog.CodeStyleCheckerDialog(self) ) self.__editorCodeStyleCheckerDialog.show() self.__editorCodeStyleCheckerDialog.start( editor.getFileName(), save=True, repeat=True )