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 ProfilerWidget(QWidget): """ Profiler widget """ DATAPATH = get_conf_path('profiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Profiler") self.output = None self.error_output = None self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Profile"), tip=_("Run profiler"), triggered=lambda : self.start(), text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = ProfilerDataTree(self) self.collapse_button = create_toolbutton(self, icon=ima.icon('collapse'), triggered=lambda dD: self.datatree.change_view(-1), tip=_('Collapse one level up')) self.expand_button = create_toolbutton(self, icon=ima.icon('expand'), triggered=lambda dD: self.datatree.change_view(1), tip=_('Expand one level down')) self.save_button = create_toolbutton(self, text_beside_icon=True, text=_("Save data"), icon=ima.icon('filesave'), triggered=self.save_data, tip=_('Save profiling data')) self.load_button = create_toolbutton(self, text_beside_icon=True, text=_("Load data"), icon=ima.icon('fileimport'), triggered=self.compare, tip=_('Load profiling data for comparison')) self.clear_button = create_toolbutton(self, text_beside_icon=True, text=_("Clear comparison"), icon=ima.icon('editdelete'), triggered=self.clear) 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.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) hlayout2.addWidget(self.save_button) hlayout2.addWidget(self.load_button) hlayout2.addWidget(self.clear_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) self.clear_button.setEnabled(False) if not is_profiler_installed(): # This should happen only on certain GNU/Linux distributions # or when this a home-made Python build because the Python # profilers are included in the Python standard library for widget in (self.datatree, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) url = 'http://docs.python.org/library/profile.html' text = '%s <a href=%s>%s</a>' % (_('Please install'), url, _("the Python profiler modules")) self.datelabel.setText(text) else: pass # self.show_data() def save_data(self): """Save data""" title = _( "Save profiler result") filename, _selfilter = getsavefilename(self, title, getcwd(), _("Profiler result")+" (*.Result)") if filename: self.datatree.save_data(filename) def compare(self): filename, _selfilter = getopenfilename(self, _("Select script to compare"), getcwd(), _("Profiler result")+" (*.Result)") if filename: self.datatree.compare(filename) self.show_data() self.clear_button.setEnabled(True) def clear(self): self.datatree.compare(None) self.datatree.hide_diff_cols(True) self.show_data() self.clear_button.setEnabled(False) def analyze(self, filename, wdir=None, args=None, pythonpath=None): if not is_profiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet 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(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename(self, _("Select Python script"), getcwd(), _("Python scripts")+" (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) 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) if pythonpath is not None: env = [to_text_string(_pth) for _pth in self.process.systemEnvironment()] baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' p_args = ['-m', 'cProfile', '-o', self.DATAPATH] if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): p_args.append(osp.normpath(filename).replace(os.sep, '/')) else: p_args.append(filename) if args: p_args.extend(shell_split(args)) executable = sys.executable if executable.endswith("spyder.exe"): # py2exe distribution executable = "python.exe" self.process.start(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) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox 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 self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.load_data(self.DATAPATH) self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)
class ProfilerWidget(QWidget): """ Profiler widget """ DATAPATH = get_conf_path('profiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Profiler") self.output = None self.error_output = None self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=get_icon('run.png'), text=_("Profile"), tip=_("Run profiler"), triggered=lambda: self.start(), text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=get_icon('stop.png'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=get_icon('fileopen.png'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=get_icon('log.png'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = ProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=get_icon('collapse.png'), triggered=lambda dD=-1: self.datatree.change_view(dD), tip=_('Collapse one level up')) self.expand_button = create_toolbutton( self, icon=get_icon('expand.png'), triggered=lambda dD=1: self.datatree.change_view(dD), tip=_('Expand one level down')) self.save_button = create_toolbutton(self, text_beside_icon=True, text=_("Save data"), icon=get_icon('filesave.png'), triggered=self.save_data, tip=_('Save profiling data')) self.load_button = create_toolbutton( self, text_beside_icon=True, text=_("Load data"), icon=get_icon('fileimport.png'), triggered=self.compare, tip=_('Load profiling data for comparison')) self.clear_button = create_toolbutton(self, text_beside_icon=True, text=_("Clear comparison"), icon=get_icon('eraser.png'), triggered=self.clear) 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.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) hlayout2.addWidget(self.save_button) hlayout2.addWidget(self.load_button) hlayout2.addWidget(self.clear_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) self.clear_button.setEnabled(False) if not is_profiler_installed(): # This should happen only on certain GNU/Linux distributions # or when this a home-made Python build because the Python # profilers are included in the Python standard library for widget in (self.datatree, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) url = 'http://docs.python.org/library/profile.html' text = '%s <a href=%s>%s</a>' % (_('Please install'), url, _("the Python profiler modules")) self.datelabel.setText(text) else: pass # self.show_data() def save_data(self): """Save data""" title = _("Save profiler result") filename, _selfilter = getsavefilename( self, title, getcwd(), _("Profiler result") + " (*.Result)") if filename: self.datatree.save_data(filename) def compare(self): filename, _selfilter = getopenfilename( self, _("Select script to compare"), getcwd(), _("Profiler result") + " (*.Result)") if filename: self.datatree.compare(filename) self.show_data() self.clear_button.setEnabled(True) def clear(self): self.datatree.compare(None) self.datatree.hide_diff_cols(True) self.show_data() self.clear_button.setEnabled(False) def analyze(self, filename, wdir=None, args=None, pythonpath=None): if not is_profiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet 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(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd(), _("Python scripts") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) 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) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in self.process.systemEnvironment() ] baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' p_args = ['-m', 'cProfile', '-o', self.DATAPATH] if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): p_args.append(osp.normpath(filename).replace(os.sep, '/')) else: p_args.append(filename) if args: p_args.extend(shell_split(args)) executable = sys.executable if executable.endswith("spyder.exe"): # py2exe distribution executable = "python.exe" self.process.start(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) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox 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 self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.load_data(self.DATAPATH) self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)
class MemoryProfilerWidget(QWidget): """ Memory profiler widget """ DATAPATH = get_conf_path('memoryprofiler.results') VERSION = '0.0.1' def __init__(self, parent): QWidget.__init__(self, parent) self.setWindowTitle("Memory profiler") self.output = None self.error_output = None self.use_colors = True self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton( self, icon=get_icon('run.png'), text=_("Profile memory usage"), tip=_("Run memory profiler"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton( self, icon=get_icon('terminate.png'), text=_("Stop"), tip=_("Stop current profiling"), 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) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton( self, icon=get_icon('fileopen.png'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton( self, icon=get_icon('log.png'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = MemoryProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=get_icon('collapse.png'), triggered=lambda dD=-1: self.datatree.collapseAll(), tip=_('Collapse all')) self.expand_button = create_toolbutton( self, icon=get_icon('expand.png'), triggered=lambda dD=1: self.datatree.expandAll(), tip=_('Expand all')) 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.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) if not is_memoryprofiler_installed(): for widget in (self.datatree, self.filecombo, self.log_button, self.start_button, self.stop_button, browse_button, self.collapse_button, self.expand_button): widget.setDisabled(True) text = _( '<b>Please install the <a href="%s">memory_profiler module</a></b>' ) % WEBSITE_URL self.datelabel.setText(text) self.datelabel.setOpenExternalLinks(True) else: pass # self.show_data() def analyze(self, filename, wdir=None, args=None, pythonpath=None, use_colors=True): self.use_colors = use_colors if not is_memoryprofiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet 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(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.emit(SIGNAL('redirect_stdio(bool)'), False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd(), _("Python scripts")+" (*.py ; *.pyw)") self.emit(SIGNAL('redirect_stdio(bool)'), False) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Memory profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Memory profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) 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) if pythonpath is not None: env = [to_text_string(_pth) for _pth in self.process.systemEnvironment()] baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath) self.process.setEnvironment(env) self.output = '' self.error_output = '' # remove previous results, since memory_profiler appends to output file # instead of replacing if osp.isfile(self.DATAPATH): os.remove(self.DATAPATH) if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): filename = osp.normpath(filename).replace(os.sep, '/') p_args = ['-m', 'memory_profiler', '-o', '"' + self.DATAPATH + '"', '"' + filename + '"'] if args: p_args.extend(programs.shell_split(args)) executable = programs.find_program('python') executable += ' ' + ' '.join(p_args) executable = executable.replace(os.sep, '/') self.process.start(executable) else: p_args = ['-m', 'memory_profiler', '-o', self.DATAPATH, filename] if args: p_args.extend(programs.shell_split(args)) executable = 'python' self.process.start(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): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox 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 self.datatree.load_data(self.DATAPATH) self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) 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)
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 ProfilerWidget(QWidget): """ Profiler widget """ DATAPATH = get_conf_path('.profiler.results') VERSION = '0.0.1' def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Profiler") self.output = None self.error_output = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=get_icon('run.png'), text=translate('Profiler', "Profile"), tip=translate('Profiler', "Run profiler"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=get_icon('terminate.png'), text=translate('Profiler', "Stop"), tip=translate('Profiler', "Stop current profiling"), 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) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=get_icon('fileopen.png'), tip=translate('Profiler', 'Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=get_icon('log.png'), text=translate('Profiler', "Output"), text_beside_icon=True, tip=translate('Profiler', "Show program's output"), triggered=self.show_log) self.datatree = ProfilerDataTree(self) self.collapse_button = create_toolbutton(self, icon=get_icon('collapse.png'), triggered=lambda dD=-1:self.datatree.change_view(dD), tip='Collapse one level up') self.expand_button = create_toolbutton(self, icon=get_icon('expand.png'), triggered=lambda dD=1:self.datatree.change_view(dD), tip='Expand one level down') 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.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) if not is_profiler_installed(): for widget in (self.datatree, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) if os.name == 'nt' \ and programs.is_module_installed("profile"): # The following is a comment from the pylint plugin: # Module is installed but script is not in PATH # (AFAIK, could happen only on Windows) text = translate('Profiler', 'Profiler script was not found. Please add "%s" to PATH.') text = unicode(text) % os.path.join(sys.prefix, "Scripts") else: text = translate('Profiler', ('Please install the modules '+ '<b>profile</b> and <b>pstats</b>:')) # FIXME: need the actual website url = 'http://www.python.org' text += ' <a href=%s>%s</a>' % (url, url) self.datelabel.setText(text) else: pass # self.show_data() def analyze(self, filename): if not is_profiler_installed(): return filename = unicode(filename) # filename is a QString instance self.kill_if_running() #index, _data = self.get_data(filename) index=None # FIXME: storing data is not implemented yet 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 = QFileDialog.getOpenFileName(self, translate('Profiler', "Select Python script"), os.getcwdu(), translate('Profiler', "Python scripts")+" (*.py ; *.pyw)") self.emit(SIGNAL('redirect_stdio(bool)'), False) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=translate('Profiler', "Profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=translate('Profiler', "Profiler output"), readonly=True, size=(700, 500)).exec_() def start(self): self.datelabel.setText('Profiling, please wait...') filename = unicode(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(os.path.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 = [os.path.basename(filename)] # FIXME: Use the system path to 'python' as opposed to hardwired p_args = ['-m', PROFILER_PATH, '-o', self.DATAPATH, os.path.basename(filename)] self.process.start('python', p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, translate('Profiler', "Error"), translate('Profiler', "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) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox 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 self.datatree.load_data(self.DATAPATH) self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M",time.localtime()) self.datelabel.setText(date_text)
class LineProfilerWidget(QWidget): """ Line profiler widget """ DATAPATH = get_conf_path('lineprofiler.results') VERSION = '0.0.1' def __init__(self, parent): QWidget.__init__(self, parent) self.setWindowTitle("Line profiler") self.output = None self.error_output = None self.use_colors = True self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=get_icon('run.png'), text=_("Profile by line"), tip=_("Run line profiler"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=get_icon('terminate.png'), text=_("Stop"), tip=_("Stop current profiling"), 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) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=get_icon('fileopen.png'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=get_icon('log.png'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = LineProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=get_icon('collapse.png'), triggered=lambda dD=-1: self.datatree.collapseAll(), tip=_('Collapse all')) self.expand_button = create_toolbutton( self, icon=get_icon('expand.png'), triggered=lambda dD=1: self.datatree.expandAll(), tip=_('Expand all')) 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.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) if not is_lineprofiler_installed(): for widget in (self.datatree, self.filecombo, self.log_button, self.start_button, self.stop_button, browse_button, self.collapse_button, self.expand_button): widget.setDisabled(True) text = _( '<b>Please install the <a href="%s">line_profiler module</a></b>' ) % WEBSITE_URL self.datelabel.setText(text) self.datelabel.setOpenExternalLinks(True) else: pass # self.show_data() def analyze(self, filename, wdir=None, args=None, pythonpath=None, use_colors=True): self.use_colors = use_colors if not is_lineprofiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet 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(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.emit(SIGNAL('redirect_stdio(bool)'), False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd(), _("Python scripts") + " (*.py ; *.pyw)") self.emit(SIGNAL('redirect_stdio(bool)'), False) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Line profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Line profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) 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) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in self.process.systemEnvironment() ] baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath) self.process.setEnvironment(env) self.output = '' self.error_output = '' p_args = ['-lvb', '-o', self.DATAPATH, filename] if args: p_args.extend(programs.shell_split(args)) if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): filename = osp.normpath(filename).replace(os.sep, '/') script_path = programs.find_program('kernprof.py') executable = '{0} {1}'.format(sys.executable, script_path) executable += ' ' + ' '.join(p_args) executable = executable.replace(os.sep, '/') self.process.start(executable) else: executable = 'kernprof.py' self.process.start(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): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox 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 self.datatree.load_data(self.DATAPATH) self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)