--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyLintInterface/PyLintConfigDialog.py Wed Jun 02 18:27:07 2021 +0200 @@ -0,0 +1,369 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2005 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to configure the PyLint process. +""" + +import os +import copy + +from PyQt6.QtCore import pyqtSlot, QProcess +from PyQt6.QtWidgets import QDialog + +from EricWidgets.EricApplication import ericApp +from EricWidgets import EricMessageBox +from EricWidgets.EricPathPicker import EricPathPickerModes + +from .Ui_PyLintConfigDialog import Ui_PyLintConfigDialog + +import Preferences + + +class PyLintConfigDialog(QDialog, Ui_PyLintConfigDialog): + """ + Class implementing a dialog to configure the PyLint process. + """ + def __init__(self, ppath, exe, parms, version): + """ + Constructor + + @param ppath project path; used to set the default path for the + rcfile picker + @type str + @param exe name of the pylint executable + @type str + @param parms parameters to set in the dialog + @type dict + @param version pylint version (unused) + @type str + """ + super().__init__(None) + self.setupUi(self) + + self.__version = version + self.__pylintProc = None + self.__lint = exe + + self.__initializeDefaults() + + # get a copy of the defaults to store the user settings + self.parameters = copy.deepcopy(self.defaults) + + # combine it with the values of parms + if parms is not None: + self.parameters.update(parms) + + self.configfilePicker.setWindowTitle( + self.tr("Select configuration file")) + self.configfilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) + self.configfilePicker.setFilters(self.tr( + "Configuration Files (*.cfg *.cnf *.rc);;" + "All Files (*)" + )) + self.configfilePicker.setDefaultDirectory(ppath) + + self.reportfilePicker.setWindowTitle( + self.tr("Select report file")) + self.reportfilePicker.setMode(EricPathPickerModes.SAVE_FILE_MODE) + self.reportfilePicker.setFilters(self.tr( + "HTML Files (*.html);;" + "Report Files (*.rpt);;" + "Text Files (*.txt);;" + "All Files (*)" + )) + + + # initialize general tab + self.configfilePicker.setText(self.parameters['configFile']) + self.txtOutputButton.setChecked(self.parameters['txtReport']) + self.htmlOutputButton.setChecked(self.parameters['htmlReport']) + self.dialogOutputButton.setChecked(self.parameters['dialogReport']) + self.reportfilePicker.setText(self.parameters['reportFile']) + + # initialize checkers tab + self.basicCheckBox.setChecked(self.parameters['enableBasic']) + self.classesCheckBox.setChecked(self.parameters['enableClasses']) + self.designCheckBox.setChecked(self.parameters['enableDesign']) + self.exceptionsCheckBox.setChecked(self.parameters['enableExceptions']) + self.formatCheckBox.setChecked(self.parameters['enableFormat']) + self.importsCheckBox.setChecked(self.parameters['enableImports']) + self.metricsCheckBox.setChecked(self.parameters['enableMetrics']) + self.miscellaneousCheckBox.setChecked( + self.parameters['enableMiscellaneous']) + self.newstyleCheckBox.setChecked(self.parameters['enableNewstyle']) + self.similaritiesCheckBox.setChecked( + self.parameters['enableSimilarities']) + self.typecheckCheckBox.setChecked(self.parameters['enableTypecheck']) + self.variablesCheckBox.setChecked(self.parameters['enableVariables']) + self.loggingCheckBox.setChecked(self.parameters['enableLogging']) + self.stringFormatCheckBox.setChecked( + self.parameters['enableStringFormat']) + + # initialize messages tab + self.enabledMessagesEdit.setText(self.parameters['enabledMessages']) + self.disabledMessagesEdit.setText(self.parameters['disabledMessages']) + + def __initializeDefaults(self): + """ + Private method to set the default values. + + These are needed later on to generate the commandline + parameters. + """ + self.defaults = { + # general options + 'configFile': '', + 'reportFile': '', + 'txtReport': False, + 'htmlReport': True, + 'dialogReport': False, + + # enabled checkers + 'enableBasic': True, + 'enableClasses': True, + 'enableDesign': True, + 'enableExceptions': True, + 'enableFormat': False, + 'enableImports': False, + 'enableLogging': True, + 'enableMetrics': True, + 'enableMiscellaneous': True, + 'enableNewstyle': True, + 'enableSimilarities': True, + 'enableStringFormat': True, + 'enableTypecheck': True, + 'enableVariables': True, + + # messages + 'enabledMessages': '', + 'disabledMessages': '', + } + + def generateParameters(self): + """ + Public method that generates the commandline parameters. + + It generates a list of strings to be used + to set the QProcess arguments for the pylint call and + a list containing the non default parameters. The second + list can be passed back upon object generation to overwrite + the default settings. + + <b>Note</b>: The arguments list contains the name of the pylint + executable as the first parameter. + + @return a tuple of the commandline parameters and non default + parameters + @rtype tuple of (list of str, dict) + """ + parms = {} + args = [] + + # 1. the program name + args.append(self.__lint) + + # 2. the commandline options + # 2.1 general options + if self.parameters['configFile'] != self.defaults['configFile']: + parms['configFile'] = self.parameters['configFile'] + args.append('--rcfile={0}'.format(self.parameters['configFile'])) + parms['txtReport'] = self.parameters['txtReport'] + parms['htmlReport'] = self.parameters['htmlReport'] + parms['dialogReport'] = self.parameters['dialogReport'] + if self.parameters['htmlReport']: + args.append('--output-format=html') + elif self.parameters['dialogReport']: + args.append('--output-format=parseable') + args.append('--reports=n') + else: + args.append('--output-format=text') + if self.parameters['reportFile'] != self.defaults['reportFile']: + parms['reportFile'] = self.parameters['reportFile'] + + # 2.2 checkers options + parms['enableBasic'] = self.parameters['enableBasic'] + parms['enableClasses'] = self.parameters['enableClasses'] + parms['enableDesign'] = self.parameters['enableDesign'] + parms['enableExceptions'] = self.parameters['enableExceptions'] + parms['enableFormat'] = self.parameters['enableFormat'] + parms['enableImports'] = self.parameters['enableImports'] + parms['enableMetrics'] = self.parameters['enableMetrics'] + parms['enableMiscellaneous'] = self.parameters['enableMiscellaneous'] + parms['enableNewstyle'] = self.parameters['enableNewstyle'] + parms['enableSimilarities'] = self.parameters['enableSimilarities'] + parms['enableTypecheck'] = self.parameters['enableTypecheck'] + parms['enableVariables'] = self.parameters['enableVariables'] + parms['enableLogging'] = self.parameters['enableLogging'] + parms['enableStringFormat'] = self.parameters['enableStringFormat'] + + checkers = [] + if self.parameters['enableBasic']: + checkers.append('basic') + if self.parameters['enableClasses']: + checkers.append('classes') + if self.parameters['enableDesign']: + checkers.append('design') + if self.parameters['enableExceptions']: + checkers.append('exceptions') + if self.parameters['enableFormat']: + checkers.append('format') + if self.parameters['enableImports']: + checkers.append('imports') + if self.parameters['enableMetrics']: + checkers.append('metrics') + if self.parameters['enableMiscellaneous']: + checkers.append('miscellaneous') + if self.parameters['enableNewstyle']: + checkers.append('newstyle') + if self.parameters['enableSimilarities']: + checkers.append('similarities') + if self.parameters['enableTypecheck']: + checkers.append('typecheck') + if self.parameters['enableVariables']: + checkers.append('variables') + if self.parameters['enableLogging']: + checkers.append('logging') + if self.parameters['enableStringFormat']: + checkers.append('string') + + args.append('--disable=all') + if checkers: + args.append('--enable={0}'.format(','.join(checkers))) + + # 2.3 messages options + parms['enabledMessages'] = self.parameters['enabledMessages'] + parms['disabledMessages'] = self.parameters['disabledMessages'] + if parms['enabledMessages']: + args.append('--enable={0}'.format(parms['enabledMessages'])) + if parms['disabledMessages']: + args.append('--disable={0}'.format(parms['disabledMessages'])) + + return (args, parms) + + def accept(self): + """ + Public slot called by the Ok button. + + It saves the values in the parameters dictionary. + """ + # get data of general tab + self.parameters['configFile'] = self.configfilePicker.text() + self.parameters['txtReport'] = self.txtOutputButton.isChecked() + self.parameters['htmlReport'] = self.htmlOutputButton.isChecked() + self.parameters['dialogReport'] = self.dialogOutputButton.isChecked() + self.parameters['reportFile'] = self.reportfilePicker.text() + + # get data of checkers tab + self.parameters['enableBasic'] = self.basicCheckBox.isChecked() + self.parameters['enableClasses'] = self.classesCheckBox.isChecked() + self.parameters['enableDesign'] = self.designCheckBox.isChecked() + self.parameters['enableExceptions'] = ( + self.exceptionsCheckBox.isChecked()) + self.parameters['enableFormat'] = self.formatCheckBox.isChecked() + self.parameters['enableImports'] = self.importsCheckBox.isChecked() + self.parameters['enableMetrics'] = self.metricsCheckBox.isChecked() + self.parameters['enableMiscellaneous'] = ( + self.miscellaneousCheckBox.isChecked()) + self.parameters['enableNewstyle'] = self.newstyleCheckBox.isChecked() + self.parameters['enableSimilarities'] = ( + self.similaritiesCheckBox.isChecked()) + self.parameters['enableTypecheck'] = self.typecheckCheckBox.isChecked() + self.parameters['enableVariables'] = self.variablesCheckBox.isChecked() + self.parameters['enableLogging'] = self.loggingCheckBox.isChecked() + self.parameters['enableStringFormat'] = ( + self.stringFormatCheckBox.isChecked()) + + # get data of messages tab + self.parameters['enabledMessages'] = ','.join( + [m.strip() for m in self.enabledMessagesEdit.text().split(',')]) + self.parameters['disabledMessages'] = ','.join( + [m.strip() for m in self.disabledMessagesEdit.text().split(',')]) + + # call the accept slot of the base class + super().accept() + + ########################################################################### + ## Methods below are needed to generate a configuration file template + ########################################################################### + + @pyqtSlot() + def on_configButton_clicked(self): + """ + Private slot to handle the generation of a sample configuration. + """ + self.buf = "" + self.__pylintProc = QProcess() + args = [] + + self.__ioEncoding = Preferences.getSystem("IOEncoding") + + args.append('--generate-rcfile') + + self.__pylintProc.readyReadStandardOutput.connect(self.__readStdout) + self.__pylintProc.readyReadStandardError.connect(self.__readStderr) + self.__pylintProc.finished.connect(self.__createConfigDone) + + self.__pylintProc.start(self.__lint, args) + procStarted = self.__pylintProc.waitForStarted() + if procStarted: + ericApp().getObject("ViewManager").enableEditorsCheckFocusIn(False) + else: + EricMessageBox.critical( + self, + self.tr('Process Generation Error'), + self.tr( + 'Could not start {0}.<br>' + 'Ensure that it is in the search path.' + ).format(self.__lint)) + + def __createConfigDone(self, exitCode, exitStatus): + """ + Private slot to handle the the finished signal of the pylint process. + + @param exitCode exit code of the process + @type int + @param exitStatus exit status of the process + @type QProcess.ExitStatus + """ + vm = ericApp().getObject("ViewManager") + vm.enableEditorsCheckFocusIn(True) + if exitStatus == QProcess.ExitStatus.NormalExit and exitCode == 0: + vm.newEditor() + aw = vm.activeWindow() + aw.insertAt(self.buf, 0, 0) + aw.setLanguage('dummy.rc') + self.reject() + + def __readStdout(self): + """ + Private slot to handle the readyReadStandardOutput signal of the + pylint process. + """ + if self.__pylintProc is None: + return + + self.__pylintProc.setReadChannel( + QProcess.ProcessChannel.StandardOutput) + + while self.__pylintProc and self.__pylintProc.canReadLine(): + line = str(self.__pylintProc.readLine(), self.__ioEncoding, + "replace").rstrip() + self.buf += line + os.linesep + + def __readStderr(self): + """ + Private slot to handle the readyReadStandardError signal of the + pylint process. + """ + if self.__pylintProc is None: + return + + self.__pylintProc.setReadChannel( + QProcess.ProcessChannel.StandardError) + while self.__pylintProc and self.__pylintProc.canReadLine(): + s = 'pylint: ' + str( + self.__pylintProc.readLine(), self.__ioEncoding, "replace") + ericApp().getObject("UserInterface").appendStderr.emit(s)