Пример #1
0
    def __init__(self, parent=None):
        """
        Initialize main widget.

        Create a basic layout and add ReportWidget.
        """
        SpyderPluginWidget.__init__(self, parent)
        self.main = parent  # Spyder 3 compatibility
        self.render_action = None
        self.save_action = None
        self.save_as_action = None

        # Initialize plugin
        self.initialize_plugin()

        # Create widget and add to dockwindow
        self.report_widget = ReportsWidget(
            self.main, [self.save_action, self.save_as_action])
        layout = QVBoxLayout()
        layout.addWidget(self.report_widget)
        self.setLayout(layout)

        self.sig_render_started.connect(self.report_widget.render_started)
        self.sig_render_progress.connect(self.report_widget.update_progress)
        self.sig_render_finished.connect(self.report_widget.render_finished)

        self.report_widget.tabs.currentChanged.connect(
            self.update_actions_status)

        # This worker runs in a thread to avoid blocking when rendering
        # a report
        self._worker_manager = WorkerManager()

        # Dict to save reports information:
        self._reports = defaultdict(Report)
Пример #2
0
    def __init__(self, parent, statusbar, icon=None, interpreter=None):
        """Status bar widget for displaying the current conda environment."""
        self._interpreter = interpreter
        super(InterpreterStatus, self).__init__(parent, statusbar, icon=icon)

        self.main = parent
        self.env_actions = []
        self.path_to_env = {}
        self.envs = {}
        self.value = ''

        self.menu = QMenu(self)
        self.sig_clicked.connect(self.show_menu)

        # Worker to compute envs in a thread
        self._worker_manager = WorkerManager(max_threads=1)

        # Timer to get envs every minute
        self._get_envs_timer = QTimer(self)
        self._get_envs_timer.setInterval(60000)
        self._get_envs_timer.timeout.connect(self.get_envs)
        self._get_envs_timer.start()

        # Update the list of envs at startup
        self.get_envs()
Пример #3
0
 def __init__(self, parent, statusbar):
     super(VCSStatus, self).__init__(parent,
                                     statusbar,
                                     icon=ima.icon('code_fork'))
     self._worker_manager = WorkerManager(max_threads=1)
     self._git_is_working = None
     self._git_job_queue = None
     self._last_git_job = None
Пример #4
0
class VCSStatus(StatusBarWidget):
    """Status bar widget for system vcs."""
    def __init__(self, parent, statusbar):
        super(VCSStatus, self).__init__(parent,
                                        statusbar,
                                        icon=ima.icon('code_fork'))
        self._worker_manager = WorkerManager(max_threads=1)
        self._git_is_working = None
        self._git_job_queue = None
        self._last_git_job = None

    def update_vcs_state(self, idx, fname, fname2):
        """Update vcs status."""
        self.update_vcs(fname, None)

    def update_vcs(self, fname, index, force=False):
        """Update vcs status."""
        if self._last_git_job == (fname, index) and not force:
            self._git_job_queue = None
            return

        if self._git_is_working:
            self._git_job_queue = (fname, index)
        else:
            self._worker_manager.terminate_all()
            worker = self._worker_manager.create_python_worker(
                self.get_git_refs, fname)
            worker.sig_finished.connect(self.process_git_data)
            self._last_git_job = (fname, index)
            self._git_job_queue = None
            self._git_is_working = True
            worker.start()

    def get_git_refs(self, fname):
        """Get Git active branch, state, branches (plus tags)."""
        return get_git_refs(osp.dirname(fname))

    def process_git_data(self, worker, output, error):
        """Receive data from git and update gui."""
        branches, branch, files_modified = output

        text = branch if branch else ''
        if len(files_modified):
            text = text + ' [{}]'.format(len(files_modified))
        self.setVisible(bool(branch))
        self.set_value(text)

        self._git_is_working = False
        if self._git_job_queue:
            self.update_vcs(*self._git_job_queue)

    def change_branch(self):
        """Change current branch."""
        pass

    def get_tooltip(self):
        """Return localized tool tip for widget."""
        return _("Git branch")
Пример #5
0
class ReportsPlugin(SpyderPluginWidget):
    """Reports plugin."""

    CONF_SECTION = 'reports'
    CONFIGWIDGET_CLASS = None
    sig_render_started = Signal(str)  # input filename

    # Success, output filename(str), error(str)
    # When an error happened returns input filename instead
    sig_render_finished = Signal(bool, object, object)

    sig_render_progress = Signal(str)

    def __init__(self, parent=None):
        """
        Initialize main widget.

        Create a basic layout and add ReportWidget.
        """
        SpyderPluginWidget.__init__(self, parent)
        self.main = parent  # Spyder 3 compatibility
        self.render_action = None
        self.save_action = None
        self.save_as_action = None

        # Initialize plugin
        self.initialize_plugin()

        # Create widget and add to dockwindow
        self.report_widget = ReportsWidget(
            self.main, [self.save_action, self.save_as_action])
        layout = QVBoxLayout()
        layout.addWidget(self.report_widget)
        self.setLayout(layout)

        self.sig_render_started.connect(self.report_widget.render_started)
        self.sig_render_progress.connect(self.report_widget.update_progress)
        self.sig_render_finished.connect(self.report_widget.render_finished)

        self.report_widget.tabs.currentChanged.connect(
            self.update_actions_status)

        # This worker runs in a thread to avoid blocking when rendering
        # a report
        self._worker_manager = WorkerManager()

        # Dict to save reports information:
        self._reports = defaultdict(Report)

    # --- SpyderPluginWidget API ----------------------------------------------
    def get_plugin_title(self):
        """Return widget title."""
        return "Reports"

    def refresh_plugin(self):
        """Refresh Reports widget."""
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin."""
        self.render_action = create_action(self,
                                           "Render report to HTML",
                                           icon=self.get_plugin_icon(),
                                           triggered=self.run_reports_render)

        self.save_action = create_action(self,
                                         "Save Report...",
                                         icon=ima.icon('filesave'),
                                         triggered=self.save_report)

        self.save_as_action = create_action(
            self,
            "Save Report as...",
            icon=ima.icon('filesaveas'),
            triggered=lambda: self.save_report(new_path=True))
        self.main.run_menu_actions += [self.render_action]

        return [self.render_action, self.save_action, self.save_as_action]

    def register_plugin(self):
        """Register plugin in Spyder's main window."""
        self.main.add_dockwidget(self)

        # Render welcome.md in a temp location
        self.render_report_thread(WELCOME_PATH)

        self.save_action.setEnabled(False)
        self.save_as_action.setEnabled(False)

    def on_first_registration(self):
        """Action to be performed on first plugin registration."""
        self.main.tabify_plugins(self.main.help, self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings."""
        pass

    def check_compatibility(self):
        """
        Check plugin requirements.

        - PyQt version is greater or equal to 5.
        """
        messages = []
        valid = True
        if PYQT4 or PYSIDE:
            messages.append('Spyder-reports does not work with Qt4')
            valid = False
        return valid, ", ".join(messages)

    # -------------------------------------------------------------------------

    def update_actions_status(self):
        """Disable/enable actions, avoiding the welcome page to be saved."""
        report = self.report_widget.get_focus_report()
        enabled = report is not None and report != WELCOME_PATH

        self.save_action.setEnabled(enabled)
        self.save_as_action.setEnabled(enabled)

    def check_create_tmp_dir(self, folder):
        """Create temp dir if it does not exists."""
        if not os.path.exists(folder):
            os.makedirs(folder)

    def show_error_message(self, message):
        """Show error message."""
        messageBox = QMessageBox(self)
        messageBox.setWindowModality(Qt.NonModal)
        messageBox.setAttribute(Qt.WA_DeleteOnClose)
        messageBox.setWindowTitle('Render Report Error')
        messageBox.setText(message)
        messageBox.setStandardButtons(QMessageBox.Ok)
        messageBox.show()

    def save_report(self, new_path=False):
        """Save report.

        If the report was already saved, save it in the same path.

        If the output are several files copy temporary dir to user
        selected directory.

        If the output is just one file copy it, to the user selected path.

        Args:
            new_path: force saving in a new path
        """
        report_filename = self.report_widget.get_focus_report()
        if report_filename is None:
            return
        report = self._reports[report_filename]

        output_filename = report.render_dir
        input_dir, _ = osp.split(report_filename)
        tmpdir, output_fname = osp.split(output_filename)

        output = None if new_path else report.save_path

        # TODO This should be improved because Pweave creates a
        # figures dir even when there isn't figures causing this
        # to evaluate always to True
        if len([name for name in os.listdir(tmpdir)]) > 1:
            # if there is more than one file save a dir
            if output is None:
                output = getexistingdirectory(parent=self,
                                              caption='Save Report',
                                              basedir=input_dir)
                if not osp.isdir(output):
                    return
                report.save_path = output
            # Using distutils instead of shutil.copytree
            # because shutil.copytree fails if the dir already exists
            copy_tree(tmpdir, output)
        else:
            if output is None:
                basedir = osp.join(input_dir, output_fname)
                output, _ = getsavefilename(parent=self,
                                            caption='Save Report',
                                            basedir=basedir)
                if not osp.isfile(output):
                    return
                report.save_path = output
            shutil.copy(output_filename, output)

    def run_reports_render(self):
        """Call report rendering and displays its output."""
        editorstack = self.main.editor.get_current_editorstack()
        if editorstack.save():
            fname = osp.abspath(self.main.editor.get_current_filename())
            self.render_report_thread(fname)
        self.switch_to_plugin()

    def render_report_thread(self, file_name):
        """Render report in a thread and update the widget when finished."""
        def worker_output(worker, output_file, error):
            """Receive the worker output, and update the widget."""
            if error is None and output_file:
                self.report_widget.set_html_from_file(output_file, file_name)
                self.sig_render_finished.emit(True, output_file, None)
            else:
                self.show_error_message(to_text_string(error))
                self.sig_render_finished.emit(False, file_name,
                                              to_text_string(error))

        # Before starting a new worker process make sure to end previous
        # incarnations
        self._worker_manager.terminate_all()

        worker = self._worker_manager.create_python_worker(
            self._render_report,
            file_name,
        )
        worker.sig_finished.connect(worker_output)
        self.sig_render_started.emit(file_name)
        worker.start()

    def _render_report(self, file, output=None):
        """
        Parse report document using pweave.

        Args:
            file: Path of the file to be parsed.

        Return:
            Output file path
        """
        if output is None:
            report = self._reports.get(file)
            if report is not None:
                output = report.render_dir
            if output is None:
                name = osp.splitext(osp.basename(file))[0]
                id_ = to_text_string(uuid.uuid4())
                output = osp.join(REPORTS_TEMPDIR, id_, '{}.html'.format(name))
                self._reports[file].render_dir = output

        folder = osp.split(output)[0]
        self.check_create_tmp_dir(folder)
        doc = Pweb(file, output=output)

        # TODO Add more formats support
        if doc.file_ext == '.mdw':
            _format = 'md2html'
        elif doc.file_ext == '.md':
            _format = 'pandoc2html'
        else:
            raise Exception("Format not supported ({})".format(doc.file_ext))

        f = CaptureStdOutput(self.sig_render_progress)

        if pweave_version.startswith('0.3'):
            with redirect_stdout(f):
                self.sig_render_progress.emit("Readign")
                doc.read()
                self.sig_render_progress.emit("Running")
                doc.run()
                self.sig_render_progress.emit("Formating")
                doc.format(doctype=_format)
                self.sig_render_progress.emit("Writing")
                doc.write()
            return doc.sink
        else:
            with redirect_stdout(f):
                doc.setformat(_format)
                doc.detect_reader()
                doc.parse()
                doc.run()
                doc.format()
                doc.write()
            return doc.sink
Пример #6
0
class InterpreterStatus(BaseTimerStatus):
    """Status bar widget for displaying the current conda environment."""
    ID = 'interpreter_status'
    CONF_SECTION = 'main_interpreter'

    sig_open_preferences_requested = Signal()
    """
    Signal to open the main interpreter preferences.
    """
    def __init__(self, parent, icon=None, interpreter=None):
        """Status bar widget for displaying the current conda environment."""
        self._interpreter = interpreter
        super().__init__(parent)
        self.main = parent
        self.env_actions = []
        self.path_to_env = {}
        self.envs = {}
        self.value = ''

        # Worker to compute envs in a thread
        self._worker_manager = WorkerManager(max_threads=1)

        # Timer to get envs every minute
        self._get_envs_timer = QTimer(self)
        self._get_envs_timer.setInterval(60000)
        self._get_envs_timer.timeout.connect(self.get_envs)
        self._get_envs_timer.start()

        # Update the list of envs at startup
        self.get_envs()

    # ---- BaseTimerStatus API
    def get_value(self):
        """
        Switch to default interpreter if current env was removed or
        update Python version of current one.
        """
        env_dir = self._get_env_dir(self._interpreter)

        if not osp.isdir(env_dir):
            # Env was removed on Mac or Linux
            self._on_interpreter_removed()
        elif not osp.isfile(self._interpreter):
            # This can happen on Windows because the interpreter was
            # renamed to .conda_trash
            if not osp.isdir(osp.join(env_dir, 'conda-meta')):
                # If conda-meta is missing, it means the env was removed
                self._on_interpreter_removed()
            else:
                # If not, it means the interpreter is being updated so
                # we need to update its version
                self.get_envs()
        else:
            # We need to do this in case the Python version was
            # changed in the env
            if self._interpreter in self.path_to_env:
                self.update_interpreter()

        return self.value

    # ---- Qt reimplemented methods
    def closeEvent(self, event):
        self._get_envs_timer.stop()
        self._worker_manager.terminate_all()
        super().closeEvent(event)

    # ---- Private API
    def _get_env_dir(self, interpreter):
        """Get env directory from interpreter executable."""
        if os.name == 'nt':
            return osp.dirname(interpreter)
        else:
            return osp.dirname(osp.dirname(interpreter))

    def _get_envs(self):
        """Get the list of environments in the system."""
        # Compute info of default interpreter to have it available in
        # case we need to switch to it. This will avoid lags when
        # doing that in get_value.
        if sys.executable not in self.path_to_env:
            self._get_env_info(sys.executable)

        # Get envs
        conda_env = get_list_conda_envs()
        pyenv_env = get_list_pyenv_envs()
        return {**conda_env, **pyenv_env}

    def _get_env_info(self, path):
        """Get environment information."""
        path = path.lower() if os.name == 'nt' else path
        try:
            name = self.path_to_env[path]
        except KeyError:
            win_app_path = osp.join('AppData', 'Local', 'Programs', 'spyder')
            if 'Spyder.app' in path or win_app_path in path:
                name = 'internal'
            elif 'conda' in path:
                name = 'conda'
            elif 'pyenv' in path:
                name = 'pyenv'
            else:
                name = 'custom'
            version = get_interpreter_info(path)
            self.path_to_env[path] = name
            self.envs[name] = (path, version)
        __, version = self.envs[name]
        return f'{name} ({version})'

    def _on_interpreter_removed(self):
        """
        Actions to take when the current custom interpreter is removed
        outside Spyder.
        """
        # NOTES:
        # 1. The interpreter will be updated when the option changes below
        # generate a change in the 'executable' one in the container.
        # 2. *Do not* change the order in which these options are set or the
        # interpreter won't be updated correctly.
        self.set_conf('custom_interpreter', ' ')
        self.set_conf('custom', False)
        self.set_conf('default', True)

    # ---- Public API
    def get_envs(self):
        """
        Get the list of environments in a thread to keep them up to
        date.
        """
        self._worker_manager.terminate_all()
        worker = self._worker_manager.create_python_worker(self._get_envs)
        worker.sig_finished.connect(self.update_envs)
        worker.start()

    def update_envs(self, worker, output, error):
        """Update the list of environments in the system."""
        self.envs.update(**output)
        for env in list(self.envs.keys()):
            path, version = self.envs[env]
            # Save paths in lowercase on Windows to avoid issues with
            # capitalization.
            path = path.lower() if os.name == 'nt' else path
            self.path_to_env[path] = env

        self.update_interpreter()

    def open_interpreter_preferences(self):
        """Request to open the main interpreter preferences."""
        self.sig_open_preferences_requested.emit()

    def update_interpreter(self, interpreter=None):
        """Set main interpreter and update information."""
        if interpreter:
            self._interpreter = interpreter
        self.value = self._get_env_info(self._interpreter)
        self.set_value(self.value)
Пример #7
0
class InterpreterStatus(BaseTimerStatus):
    """Status bar widget for displaying the current conda environment."""
    def __init__(self, parent, statusbar, icon=None, interpreter=None):
        """Status bar widget for displaying the current conda environment."""
        self._interpreter = interpreter
        super(InterpreterStatus, self).__init__(parent, statusbar, icon=icon)

        self.main = parent
        self.env_actions = []
        self.path_to_env = {}
        self.envs = {}
        self.value = ''

        self.menu = QMenu(self)
        self.sig_clicked.connect(self.show_menu)

        # Worker to compute envs in a thread
        self._worker_manager = WorkerManager(max_threads=1)

        # Timer to get envs every minute
        self._get_envs_timer = QTimer(self)
        self._get_envs_timer.setInterval(60000)
        self._get_envs_timer.timeout.connect(self.get_envs)
        self._get_envs_timer.start()

        # Update the list of envs at startup
        self.get_envs()

    def import_test(self):
        pass

    def get_value(self):
        """
        Switch to default interpreter if current env was removed or
        update Python version of current one.
        """
        env_dir = self._get_env_dir(self._interpreter)

        if not osp.isdir(env_dir):
            # Env was removed on Mac or Linux
            CONF.set('main_interpreter', 'custom', False)
            CONF.set('main_interpreter', 'default', True)
            self.update_interpreter(sys.executable)
        elif not osp.isfile(self._interpreter):
            # This can happen on Windows because the interpreter was
            # renamed to .conda_trash
            if not osp.isdir(osp.join(env_dir, 'conda-meta')):
                # If conda-meta is missing, it means the env was removed
                CONF.set('main_interpreter', 'custom', False)
                CONF.set('main_interpreter', 'default', True)
                self.update_interpreter(sys.executable)
            else:
                # If not, it means the interpreter is being updated so
                # we need to update its version
                self.get_envs()
        else:
            # We need to do this in case the Python version was
            # changed in the env
            if self._interpreter in self.path_to_env:
                self.update_interpreter()

        return self.value

    def _get_env_dir(self, interpreter):
        """Get env directory from interpreter executable."""
        if os.name == 'nt':
            return osp.dirname(interpreter)
        else:
            return osp.dirname(osp.dirname(interpreter))

    def _get_envs(self):
        """Get the list of environments in the system."""
        # Compute info of default interpreter to have it available in
        # case we need to switch to it. This will avoid lags when
        # doing that in get_value.
        if sys.executable not in self.path_to_env:
            self._get_env_info(sys.executable)

        # Get envs
        conda_env = get_list_conda_envs()
        pyenv_env = get_list_pyenv_envs()
        return {**conda_env, **pyenv_env}

    def get_envs(self):
        """
        Get the list of environments in a thread to keep them up to
        date.
        """
        self._worker_manager.terminate_all()
        worker = self._worker_manager.create_python_worker(self._get_envs)
        worker.sig_finished.connect(self.update_envs)
        worker.start()

    def update_envs(self, worker, output, error):
        """Update the list of environments in the system."""
        self.envs = output
        for env in list(self.envs.keys()):
            path, version = self.envs[env]
            # Save paths in lowercase on Windows to avoid issues with
            # capitalization.
            path = path.lower() if os.name == 'nt' else path
            self.path_to_env[path] = env

        self.update_interpreter()

    def show_menu(self):
        """Display a menu when clicking on the widget."""
        menu = self.menu
        menu.clear()
        text = _("Change default environment in Preferences...")
        change_action = create_action(
            self,
            text=text,
            triggered=self.open_interpreter_preferences,
        )
        add_actions(menu, [change_action])
        rect = self.contentsRect()
        os_height = 7 if os.name == 'nt' else 12
        pos = self.mapToGlobal(rect.topLeft() +
                               QPoint(-40, -rect.height() - os_height))
        menu.popup(pos)

    def open_interpreter_preferences(self):
        """Open the Preferences dialog in the Python interpreter section."""
        self.main.show_preferences()
        dlg = self.main.prefs_dialog_instance
        index = dlg.get_index_by_name("main_interpreter")
        dlg.set_current_index(index)

    def _get_env_info(self, path):
        """Get environment information."""
        path = path.lower() if os.name == 'nt' else path
        try:
            name = self.path_to_env[path]
        except KeyError:
            win_app_path = osp.join('AppData', 'Local', 'Programs', 'spyder')
            if 'Spyder.app' in path or win_app_path in path:
                name = 'internal'
            elif 'conda' in path:
                name = 'conda'
            elif 'pyenv' in path:
                name = 'pyenv'
            else:
                name = 'custom'
            version = get_interpreter_info(path)
            self.path_to_env[path] = name
            self.envs[name] = (path, version)
        __, version = self.envs[name]
        return '{env} ({version})'.format(env=name, version=version)

    def get_tooltip(self):
        """Override api method."""
        return self._interpreter if self._interpreter else ''

    def update_interpreter(self, interpreter=None):
        """Set main interpreter and update information."""
        if interpreter:
            self._interpreter = interpreter
        self.value = self._get_env_info(self._interpreter)
        self.set_value(self.value)
        self.update_tooltip()
Пример #8
0
 def __init__(self, parent):
     super().__init__(parent)
     self._worker_manager = WorkerManager(max_threads=1)
     self._git_is_working = None
     self._git_job_queue = None
     self._last_git_job = None