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) if PYLINT_PATH is None: for widget in (self.treewidget, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) if os.name == 'nt' \ and programs.is_module_installed("pylint"): # Pylint is installed but pylint script is not in PATH # (AFAIK, could happen only on Windows) text = _('Pylint script was not found. Please add "%s" to PATH.') text = to_text_string(text) % osp.join(sys.prefix, "Scripts") else: text = _('Please install <b>pylint</b>:') url = 'http://www.logilab.fr' text += ' <a href=%s>%s</a>' % (url, url) self.ratelabel.setText(text) else: self.show_data() def analyze(self, filename): if PYLINT_PATH is None: return 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() 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) def show_log(self): if self.output: TextEditor(self.output, title=_("Pylint output"), readonly=True, size=(700, 500)).exec_() 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: 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(PYLINT_PATH, 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.0.2' def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.output = None self.error_output = None self.max_entries = max_entries self.data = [self.VERSION] if osp.isfile(self.DATAPATH): try: data = cPickle.load(file(self.DATAPATH)) if data[0] == self.VERSION: self.data = data except EOFError: pass self.filecombo = PythonModulesComboBox(self) if self.data: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) self.start_button = create_toolbutton(self, get_icon('run.png'), translate('Pylint', "Analyze"), tip=translate('Pylint', "Run analysis"), triggered=self.start) self.stop_button = create_toolbutton(self, get_icon('terminate.png'), translate('Pylint', "Stop"), tip=translate('Pylint', "Stop current analysis")) self.connect(self.filecombo, SIGNAL('valid(bool)'), self.start_button.setEnabled) self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) browse_button = create_toolbutton(self, get_icon('fileopen.png'), tip=translate('Pylint', 'Select Python script'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, get_icon('log.png'), translate('Pylint', "Output"), tip=translate('Pylint', "Complete Pylint 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) if not is_pylint_installed(): for widget in (self.treewidget, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) text = translate('Pylint', 'Please install <b>pylint</b>:') url = 'http://www.logilab.fr' text += ' <a href=%s>%s</a>' % (url, url) self.ratelabel.setText(text) else: self.show_data() def analyze(self, filename): if not is_pylint_installed(): return filename = unicode(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(index) self.filecombo.selected() if self.filecombo.is_valid(): self.start() def select_file(self): self.emit(SIGNAL('redirect_stdio(bool)'), False) filename = QFileDialog.getOpenFileName(self, translate('Pylint', "Select Python script"), os.getcwdu(), translate('Pylint', "Python scripts")+" (*.py ; *.pyw)") self.emit(SIGNAL('redirect_stdio(bool)'), False) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.data = [self.VERSION] + \ [(filename, data) for filename, data in self.data[1:] if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.data[1:]] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.data[1:]): 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.data.pop(index) self.data.append( (filename, data) ) self.save() def set_max_entries(self, max_entries): self.max_entries = max_entries self.save() def save(self): while len(self.data) > self.max_entries+1: self.data.pop(1) cPickle.dump(self.data, file(self.DATAPATH, 'w')) def show_log(self): if self.output: TextEditor(self.output, title=translate('Pylint', "Pylint output"), readonly=True, size=(700, 500)).exec_() def start(self): filename = unicode(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.read_output) self.connect(self.process, SIGNAL("readyReadStandardError()"), lambda: self.read_output(error=True)) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self.stop_button, SIGNAL("clicked()"), self.process.kill) self.output = '' self.error_output = '' p_args = [osp.basename(filename)] self.process.start(PYLINT_PATH, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, translate('Pylint', "Error"), translate('Pylint', "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) bytes = QByteArray() while self.process.bytesAvailable(): if error: bytes += self.process.readAllStandardError() else: bytes += self.process.readAllStandardOutput() text = unicode( QString.fromLocal8Bit(bytes.data()) ) if error: self.error_output += text else: self.output += text def finished(self): self.set_running_state(False) if not self.output: 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 for prefix in results: if line.startswith(prefix): break else: continue i1 = line.find(':') if i1 == -1: continue 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) message = line[i2+1:] item = (module, line_nb, message) results[line[:i1+1]].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 = unicode(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 = unicode(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = translate('Pylint', 'Source code has not been rated yet.') self.treewidget.clear() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = translate('Pylint', 'Analysis did not succeed ' '(see output for more details).') self.treewidget.clear() 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 = translate('Pylint', 'Global evaluation:') text = (text_style % text)+(rate_style % (color, ('%s/10' % rate))) if previous_rate: text_prun = translate('Pylint', 'previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % text_prun self.treewidget.set_results(filename, results) date_text = text_style % time.strftime("%d %b %Y %H:%M", datetime) self.ratelabel.setText(text) self.datelabel.setText(date_text)
class PylintWidget(QWidget): """ Pylint widget """ DATAPATH = get_conf_path('pylint.results') VERSION = '1.1.0' 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=get_icon('run.png'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=get_icon('stop.png'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.connect(self.filecombo, SIGNAL('valid(bool)'), self.start_button.setEnabled) self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) browse_button = create_toolbutton(self, icon=get_icon('fileopen.png'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=get_icon('log.png'), 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) if PYLINT_PATH is None: for widget in (self.treewidget, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) if os.name == 'nt' \ and programs.is_module_installed("pylint"): # Pylint is installed but pylint script is not in PATH # (AFAIK, could happen only on Windows) text = _('Pylint script was not found. Please add "%s" to PATH.') text = to_text_string(text) % osp.join(sys.prefix, "Scripts") else: text = _('Please install <b>pylint</b>:') url = 'http://www.logilab.fr' text += ' <a href=%s>%s</a>' % (url, url) self.ratelabel.setText(text) else: self.show_data() def analyze(self, filename): if PYLINT_PATH is None: return 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() def select_file(self): self.emit(SIGNAL('redirect_stdio(bool)'), False) filename, _selfilter = getopenfilename(self, _("Select Python file"), getcwd(), _("Python files")+" (*.py ; *.pyw)") self.emit(SIGNAL('redirect_stdio(bool)'), False) 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) def show_log(self): if self.output: TextEditor(self.output, title=_("Pylint output"), readonly=True, size=(700, 500)).exec_() 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.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.read_output) self.connect(self.process, SIGNAL("readyReadStandardError()"), lambda: self.read_output(error=True)) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self.stop_button, SIGNAL("clicked()"), self.process.kill) self.output = '' self.error_output = '' plver = PYLINT_VER if plver is not None: 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(PYLINT_PATH, 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): 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.0.2' def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.output = None self.error_output = None self.max_entries = max_entries self.data = [self.VERSION] if osp.isfile(self.DATAPATH): try: data = cPickle.load(file(self.DATAPATH)) if data[0] == self.VERSION: self.data = data except EOFError: pass self.filecombo = PythonModulesComboBox(self) if self.data: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) self.start_button = create_toolbutton(self, get_icon('run.png'), translate('Pylint', "Analyze"), tip=translate( 'Pylint', "Run analysis"), triggered=self.start) self.stop_button = create_toolbutton(self, get_icon('terminate.png'), translate('Pylint', "Stop"), tip=translate( 'Pylint', "Stop current analysis")) self.connect(self.filecombo, SIGNAL('valid(bool)'), self.start_button.setEnabled) self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) browse_button = create_toolbutton(self, get_icon('fileopen.png'), tip=translate( 'Pylint', 'Select Python script'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, get_icon('log.png'), translate('Pylint', "Output"), tip=translate( 'Pylint', "Complete Pylint 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) if not is_pylint_installed(): for widget in (self.treewidget, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) text = translate('Pylint', 'Please install <b>pylint</b>:') url = 'http://www.logilab.fr' text += ' <a href=%s>%s</a>' % (url, url) self.ratelabel.setText(text) else: self.show_data() def analyze(self, filename): if not is_pylint_installed(): return filename = unicode(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(index) self.filecombo.selected() if self.filecombo.is_valid(): self.start() def select_file(self): self.emit(SIGNAL('redirect_stdio(bool)'), False) filename = QFileDialog.getOpenFileName( self, translate('Pylint', "Select Python script"), os.getcwdu(), translate('Pylint', "Python scripts") + " (*.py ; *.pyw)") self.emit(SIGNAL('redirect_stdio(bool)'), False) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.data = [self.VERSION] + \ [(filename, data) for filename, data in self.data[1:] if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.data[1:]] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.data[1:]): 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.data.pop(index) self.data.append((filename, data)) self.save() def set_max_entries(self, max_entries): self.max_entries = max_entries self.save() def save(self): while len(self.data) > self.max_entries + 1: self.data.pop(1) cPickle.dump(self.data, file(self.DATAPATH, 'w')) def show_log(self): if self.output: TextEditor(self.output, title=translate('Pylint', "Pylint output"), readonly=True, size=(700, 500)).exec_() def start(self): filename = unicode(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.read_output) self.connect(self.process, SIGNAL("readyReadStandardError()"), lambda: self.read_output(error=True)) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self.stop_button, SIGNAL("clicked()"), self.process.kill) self.output = '' self.error_output = '' p_args = [osp.basename(filename)] self.process.start(PYLINT_PATH, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical( self, translate('Pylint', "Error"), translate('Pylint', "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) bytes = QByteArray() while self.process.bytesAvailable(): if error: bytes += self.process.readAllStandardError() else: bytes += self.process.readAllStandardOutput() text = unicode(QString.fromLocal8Bit(bytes.data())) if error: self.error_output += text else: self.output += text def finished(self): self.set_running_state(False) if not self.output: 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 for prefix in results: if line.startswith(prefix): break else: continue i1 = line.find(':') if i1 == -1: continue 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) message = line[i2 + 1:] item = (module, line_nb, message) results[line[:i1 + 1]].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 = unicode(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 = unicode(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = translate('Pylint', 'Source code has not been rated yet.') self.treewidget.clear() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = translate( 'Pylint', 'Analysis did not succeed ' '(see output for more details).') self.treewidget.clear() 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 = translate('Pylint', 'Global evaluation:') text = (text_style % text) + (rate_style % (color, ('%s/10' % rate))) if previous_rate: text_prun = translate('Pylint', 'previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % text_prun self.treewidget.set_results(filename, results) date_text = text_style % time.strftime("%d %b %Y %H:%M", datetime) self.ratelabel.setText(text) self.datelabel.setText(date_text)