class PylintWidget(QWidget): """ Pylint widget """ DATAPATH = get_conf_path('pylint.results') VERSION = '1.1.0' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Pylint") self.output = None self.error_output = None self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) if data[0] == self.VERSION: self.rdata = data[1:] except (EOFError, ImportError): pass self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.show_data) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) else: self.start_button.setEnabled(False) def analyze(self, filename): filename = to_text_string(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python file"), getcwd_or_home(), _("Python files") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: TextEditor(self.output, title=_("Pylint output"), readonly=True, size=(700, 500)).exec_() @Slot() def start(self): filename = to_text_string(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' plver = PYLINT_VER if plver is not None: p_args = ['-m', 'pylint', '--output-format=text'] if plver.split('.')[0] == '0': p_args += ['-i', 'yes'] else: # Option '-i' (alias for '--include-ids') was removed in pylint # 1.0 p_args += ["--msg-template='{msg_id}:{line:3d},"\ "{column}: {obj}: {msg}"] p_args += [osp.basename(filename)] else: p_args = [osp.basename(filename)] self.process.start(sys.executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("pylint error:\n\n" + self.error_output, file=sys.stderr) return # Convention, Refactor, Warning, Error results = {'C:': [], 'R:': [], 'W:': [], 'E:': []} txt_module = '************* Module ' module = '' # Should not be needed - just in case something goes wrong for line in self.output.splitlines(): if line.startswith(txt_module): # New module module = line[len(txt_module):] continue # Supporting option include-ids: ('R3873:' instead of 'R:') if not re.match('^[CRWE]+([0-9]{4})?:', line): continue i1 = line.find(':') if i1 == -1: continue msg_id = line[:i1] i2 = line.find(':', i1 + 1) if i2 == -1: continue line_nb = line[i1 + 1:i2].strip() if not line_nb: continue line_nb = int(line_nb.split(',')[0]) message = line[i2 + 1:] item = (module, line_nb, message, msg_id) results[line[0] + ':'].append(item) # Rate rate = None txt_rate = 'Your code has been rated at ' i_rate = self.output.find(txt_rate) if i_rate > 0: i_rate_end = self.output.find('/10', i_rate) if i_rate_end > 0: rate = self.output[i_rate + len(txt_rate):i_rate_end] # Previous run previous = '' if rate is not None: txt_prun = 'previous run: ' i_prun = self.output.find(txt_prun, i_rate_end) if i_prun > 0: i_prun_end = self.output.find('/10', i_prun) previous = self.output[i_prun + len(txt_prun):i_prun_end] filename = to_text_string(self.filecombo.currentText()) self.set_data(filename, (time.localtime(), rate, previous, results)) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = _('Source code has not been rated yet.') self.treewidget.clear_results() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = _('Analysis did not succeed ' '(see output for more details).') self.treewidget.clear_results() date_text = '' else: text_style = "<span style=\'color: #444444\'><b>%s </b></span>" rate_style = "<span style=\'color: %s\'><b>%s</b></span>" prevrate_style = "<span style=\'color: #666666\'>%s</span>" color = "#FF0000" if float(rate) > 5.: color = "#22AA22" elif float(rate) > 3.: color = "#EE5500" text = _('Global evaluation:') text = (text_style % text) + (rate_style % (color, ('%s/10' % rate))) if previous_rate: text_prun = _('previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % text_prun self.treewidget.set_results(filename, results) date = to_text_string(time.strftime("%d %b %Y %H:%M", datetime), encoding='utf8') date_text = text_style % date self.ratelabel.setText(text) self.datelabel.setText(date_text)
class PylintWidget(QWidget): """ Pylint widget """ DATAPATH = get_conf_path('pylint.results') VERSION = '1.1.0' redirect_stdio = Signal(bool) start_analysis = Signal() def __init__(self, parent, max_entries=100, options_button=None, text_color=None, prevrate_color=None): super().__init__(parent) self.setWindowTitle("Pylint") self.output = None self.error_output = None self.filename = None self.text_color = text_color self.prevrate_color = prevrate_color self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) if data[0] == self.VERSION: self.rdata = data[1:] except (EOFError, ImportError): pass self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton( self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.analyze_button_handler, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.check_new_file) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) if options_button: hlayout1.addWidget(options_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) self.start_button.setEnabled(self.filecombo.is_valid()) else: self.start_button.setEnabled(False) def check_new_file(self): fname = self.get_filename() if fname != self.filename: self.filename = fname self.show_data() def get_filename(self): """Get current filename in combobox.""" return self.filecombo.currentText() @Slot(str) def set_filename(self, filename): """Set filename without performing code analysis.""" filename = str(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() def analyze(self, filename=None): """ Perform code analysis for given `filename`. If `filename` is None default to current filename in combobox. """ if filename is not None: self.set_filename(filename) if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python file"), getcwd_or_home(), _("Python files") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: output_dialog = TextEditor(self.output, title=_("Pylint output"), parent=self, readonly=True) output_dialog.resize(700, 500) output_dialog.exec_() @Slot() def analyze_button_handler(self): """Try to start code analysis when Analyze button pressed.""" self.start_analysis.emit() def get_pylintrc_path(self, filename): """Get the path to the most proximate pylintrc config to the file.""" parent = self.parentWidget() if parent is not None: project_dir = parent.main.projects.get_active_project_path() else: project_dir = None search_paths = [ osp.dirname(filename), # File's directory getcwd_or_home(), # Working directory project_dir, # Project directory osp.expanduser("~"), # Home directory ] return get_pylintrc_path(search_paths=search_paths) @Slot() def start(self): """Start the code analysis.""" filename = str(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(getcwd_or_home()) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' if PYLINT_VER is not None: pylint_args = [ '-m', 'pylint', '--output-format=text', '--msg-template=' "'{msg_id}:{symbol}:{line:3d},{column}: {msg}'" ] pylintrc_path = self.get_pylintrc_path(filename=filename) if pylintrc_path is not None: pylint_args += ['--rcfile={}'.format(pylintrc_path)] pylint_args.append(filename) processEnvironment = QProcessEnvironment() processEnvironment.insert("PYTHONIOENCODING", "utf8") self.process.setProcessEnvironment(processEnvironment) self.process.start(sys.executable, pylint_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = str(qba.data(), 'utf-8') if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("pylint error:\n\n" + self.error_output, file=sys.stderr) return # Convention, Refactor, Warning, Error results = {'C:': [], 'R:': [], 'W:': [], 'E:': []} txt_module = '************* Module ' module = '' # Should not be needed - just in case something goes wrong for line in self.output.splitlines(): if line.startswith(txt_module): # New module module = line[len(txt_module):] continue # Supporting option include-ids: ('R3873:' instead of 'R:') if not re.match(r'^[CRWE]+([0-9]{4})?:', line): continue items = {} idx_0 = 0 idx_1 = 0 key_names = ["msg_id", "message_name", "line_nb", "message"] for key_idx, key_name in enumerate(key_names): if key_idx == len(key_names) - 1: idx_1 = len(line) else: idx_1 = line.find(":", idx_0) if idx_1 < 0: break item = line[(idx_0):idx_1] if not item: break if key_name == "line_nb": item = int(item.split(',')[0]) items[key_name] = item idx_0 = idx_1 + 1 else: pylint_item = (module, items["line_nb"], items["message"], items["msg_id"], items["message_name"]) results[line[0] + ':'].append(pylint_item) # Rate rate = None txt_rate = 'Your code has been rated at ' i_rate = self.output.find(txt_rate) if i_rate > 0: i_rate_end = self.output.find('/10', i_rate) if i_rate_end > 0: rate = self.output[i_rate + len(txt_rate):i_rate_end] # Previous run previous = '' if rate is not None: txt_prun = 'previous run: ' i_prun = self.output.find(txt_prun, i_rate_end) if i_prun > 0: i_prun_end = self.output.find('/10', i_prun) previous = self.output[i_prun + len(txt_prun):i_prun_end] filename = str(self.filecombo.currentText()) self.set_data(filename, (time.localtime(), rate, previous, results)) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None and len(self.output) > 0) self.kill_if_running() filename = str(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = _('Source code has not been rated yet.') self.treewidget.clear_results() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = _('Analysis did not succeed ' '(see output for more details).') self.treewidget.clear_results() date_text = '' else: text_style = "<span style=\'color: %s\'><b>%s </b></span>" rate_style = "<span style=\'color: %s\'><b>%s</b></span>" prevrate_style = "<span style=\'color: %s\'>%s</span>" color = "#FF0000" if float(rate) > 5.: color = "#22AA22" elif float(rate) > 3.: color = "#EE5500" text = _('Global evaluation:') text = ((text_style % (self.text_color, text)) + (rate_style % (color, ('%s/10' % rate)))) if previous_rate: text_prun = _('previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % (self.prevrate_color, text_prun) self.treewidget.set_results(filename, results) date = time.strftime("%Y-%m-%d %H:%M:%S", datetime) date_text = text_style % (self.text_color, date) self.ratelabel.setText(text) self.datelabel.setText(date_text)
class PylintWidget(QWidget): """ Pylint widget """ DATAPATH = get_conf_path('pylint.results') VERSION = '1.1.0' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Pylint") self.output = None self.error_output = None self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) if data[0] == self.VERSION: self.rdata = data[1:] except (EOFError, ImportError): pass self.filecombo = PythonModulesComboBox(self) if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.show_data) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() def analyze(self, filename): filename = to_text_string(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count()-1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename(self, _("Select Python file"), getcwd(), _("Python files")+" (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION]+self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: TextEditor(self.output, title=_("Pylint output"), readonly=True, size=(700, 500)).exec_() @Slot() def start(self): filename = to_text_string(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect(lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' plver = PYLINT_VER if plver is not None: p_args = ['-m', 'pylint', '--output-format=text'] if plver.split('.')[0] == '0': p_args += ['-i', 'yes'] else: # Option '-i' (alias for '--include-ids') was removed in pylint # 1.0 p_args += ["--msg-template='{msg_id}:{line:3d},"\ "{column}: {obj}: {msg}"] p_args += [osp.basename(filename)] else: p_args = [osp.basename(filename)] self.process.start(sys.executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string( locale_codec.toUnicode(qba.data()) ) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("pylint error:\n\n" + self.error_output, file=sys.stderr) return # Convention, Refactor, Warning, Error results = {'C:': [], 'R:': [], 'W:': [], 'E:': []} txt_module = '************* Module ' module = '' # Should not be needed - just in case something goes wrong for line in self.output.splitlines(): if line.startswith(txt_module): # New module module = line[len(txt_module):] continue # Supporting option include-ids: ('R3873:' instead of 'R:') if not re.match('^[CRWE]+([0-9]{4})?:', line): continue i1 = line.find(':') if i1 == -1: continue msg_id = line[:i1] i2 = line.find(':', i1+1) if i2 == -1: continue line_nb = line[i1+1:i2].strip() if not line_nb: continue line_nb = int(line_nb.split(',')[0]) message = line[i2+1:] item = (module, line_nb, message, msg_id) results[line[0]+':'].append(item) # Rate rate = None txt_rate = 'Your code has been rated at ' i_rate = self.output.find(txt_rate) if i_rate > 0: i_rate_end = self.output.find('/10', i_rate) if i_rate_end > 0: rate = self.output[i_rate+len(txt_rate):i_rate_end] # Previous run previous = '' if rate is not None: txt_prun = 'previous run: ' i_prun = self.output.find(txt_prun, i_rate_end) if i_prun > 0: i_prun_end = self.output.find('/10', i_prun) previous = self.output[i_prun+len(txt_prun):i_prun_end] filename = to_text_string(self.filecombo.currentText()) self.set_data(filename, (time.localtime(), rate, previous, results)) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = _('Source code has not been rated yet.') self.treewidget.clear_results() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = _('Analysis did not succeed ' '(see output for more details).') self.treewidget.clear_results() date_text = '' else: text_style = "<span style=\'color: #444444\'><b>%s </b></span>" rate_style = "<span style=\'color: %s\'><b>%s</b></span>" prevrate_style = "<span style=\'color: #666666\'>%s</span>" color = "#FF0000" if float(rate) > 5.: color = "#22AA22" elif float(rate) > 3.: color = "#EE5500" text = _('Global evaluation:') text = (text_style % text)+(rate_style % (color, ('%s/10' % rate))) if previous_rate: text_prun = _('previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % text_prun self.treewidget.set_results(filename, results) date = to_text_string(time.strftime("%d %b %Y %H:%M", datetime), encoding='utf8') date_text = text_style % date self.ratelabel.setText(text) self.datelabel.setText(date_text)
class CoalaWidget(QWidget): """ coala Widget """ DATAPATH = get_conf_path('coala.results') VERSION = '' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100, options_button=None, text_color=None, prevrate_color=None): QWidget.__init__(self, parent) self.setWindowTitle('coala') self.output = None self.error_output = None self.text_color = text_color self.prevrate_color = prevrate_color self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) self.rdata = data[:] except (EOFError, ImportError): print('error!!') pass self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.show_data) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) if options_button: hlayout1.addWidget(options_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) self.start_button.setEnabled(self.filecombo.is_valid()) else: self.start_button.setEnabled(False) def analyze(self, filename): filename = to_text_string(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python file"), getcwd_or_home(), _("Python files") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: TextEditor(self.output, title=_("coala output"), readonly=True, size=(700, 500)).exec_() @Slot() def start(self): filename = to_text_string(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' clver = COALA_VER if clver is not None: c_args = ['-m', 'run_coala'] self.process.start(sys.executable, c_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("coala error:\n\n" + self.error_output, file=sys.stderr) return results = {'C:': []} literal_dict = ast.literal_eval(self.output) line_numbers = [] char_numbers = [] bear_values = [] msg_values = [] for line in literal_dict['C']: print(line) for i in line: line_num = re.compile('(.+)~') val = line_num.findall(i) for line_nb in val: if line_nb: line_numbers.append(line_nb) for j in line: char_num = re.compile('(.*);') val = char_num.findall(j) for char_nm in val: if char_nm: char_numbers.append(char_nm) for k in line: bear_val = re.compile('(.*):') val = bear_val.findall(k) for bear_val in val: if bear_val: bear_values.append(bear_val) for m in line: msg_val = re.compile(':(.*)') val = msg_val.findall(m) for msg_val in val: if msg_val: msg_values.append(msg_val) item = list(zip(line_numbers, char_numbers, bear_values, msg_values)) for i in item: results['C:'].append(i) filename = to_text_string(self.filecombo.currentText()) self.set_data(filename, results) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: self.treewidget.clear_results() else: results = data self.treewidget.set_results(filename, results)