ProjectPyramid/Project.py

Tue, 25 Oct 2022 09:40:12 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 25 Oct 2022 09:40:12 +0200
branch
eric7
changeset 160
41b23683d5a1
parent 159
d4e7f5a389e6
child 164
277a93891db9
permissions
-rw-r--r--

Adapted the import statements to the new structure.

# -*- coding: utf-8 -*-

# Copyright (c) 2012 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the Pyramid project support.
"""

import configparser
import contextlib
import glob
import os
import re
import subprocess  # secok
import sys

from PyQt6.QtCore import pyqtSlot, QObject, QFileInfo, QTimer, QUrl, QIODeviceBase
from PyQt6.QtGui import QDesktopServices
from PyQt6.QtWidgets import QMenu, QDialog, QInputDialog, QLineEdit
from PyQt6.QtCore import QProcess as QProcessPyQt

from eric7 import Preferences, Utilities
from eric7.EricGui.EricAction import EricAction
from eric7.EricWidgets.EricApplication import ericApp
from eric7.EricWidgets import EricMessageBox, EricFileDialog
from eric7.Globals import isWindowsPlatform

try:
    from eric7.EricGui import EricPixmapCache
except ImportError:
    from UI import PixmapCache as EricPixmapCache

from .PyramidDialog import PyramidDialog


class PyramidNoProjectSelectedError(Exception):
    """
    Exception thrown to signal, that there is no current Pyramid project.
    """

    pass


class QProcess(QProcessPyQt):
    """
    Class transforming the call arguments in case of gnome-terminal.
    """

    def start(self, cmd, args=None, mode=QIODeviceBase.OpenModeFlag.ReadWrite):
        """
        Public method to start the given program (cmd) in a new process, if
        none is already running, passing the command line arguments in args.

        @param cmd start the given program cmd
        @type str
        @param args list of parameters
        @type list of str
        @param mode access mode
        @type QIODeviceBase.OpenMode
        """
        if args is None:
            args = []

        if (
            cmd.endswith(
                ("gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal")
            )
            and "-e" in args
        ):
            index = args.index("-e") + 1
            cargs = " ".join(args[index:])
            args[index:] = [cargs]

        super().start(cmd, args, mode)

    @staticmethod
    def startDetached(cmd, args=None, path=""):
        """
        Public static method to start the given program (cmd) in a new process,
        if none is already running, passing the command line arguments in args.

        @param cmd start the given program cmd
        @type str
        @param args list of parameters
        @type list of str
        @param path new working directory
        @type str
        @return tuple of successful start and process id
        @rtype tuple of (bool, int)
        """
        if args is None:
            args = []

        if (
            cmd.endswith(
                ("gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal")
            )
            and "-e" in args
        ):
            index = args.index("-e") + 1
            cargs = " ".join(args[index:])
            args[index:] = [cargs]

        return QProcessPyQt.startDetached(cmd, args, path)


class Project(QObject):
    """
    Class implementing the Pyramid project support.
    """

    def __init__(self, plugin, iconSuffix, parent=None):
        """
        Constructor

        @param plugin reference to the plugin object
        @type ProjectPyramidPlugin
        @param iconSuffix suffix for the icons
        @type str
        @param parent parent
        @type QObject
        """
        super().__init__(parent)

        self.__plugin = plugin
        self.__iconSuffix = iconSuffix
        self.__ui = parent
        self.__ericProject = ericApp().getObject("Project")
        self.__virtualEnvManager = ericApp().getObject("VirtualEnvManager")
        self.__hooksInstalled = False

        self.__menus = {}  # dictionary with references to menus

        self.__serverProc = None

        self.__pyramidVersion = ""

        self.__migrationSummaryDialog = None

    def initActions(self):
        """
        Public method to define the Pyramid actions.
        """
        self.actions = []

        self.selectProjectAct = EricAction(
            self.tr("Current Pyramid Project"),
            "",
            0,
            0,
            self,
            "pyramid_current_project",
        )
        self.selectProjectAct.setStatusTip(
            self.tr("Selects the current Pyramid project")
        )
        self.selectProjectAct.setWhatsThis(
            self.tr(
                """<b>Current Pyramid Project</b>"""
                """<p>Selects the Pyramid project. Used for multi-project """
                """Pyramid projects to switch between the projects.</p>"""
            )
        )
        self.selectProjectAct.triggered.connect(self.__selectProject)
        self.actions.append(self.selectProjectAct)

        ###############################
        ## create actions below      ##
        ###############################

        self.createProjectAct = EricAction(
            self.tr("Create Pyramid Project"),
            self.tr("Create Pyramid &Project"),
            0,
            0,
            self,
            "pyramid_create_project",
        )
        self.createProjectAct.setStatusTip(self.tr("Creates a new Pyramid project"))
        self.createProjectAct.setWhatsThis(
            self.tr(
                """<b>Create Pyramid Project</b>"""
                """<p>Creates a new Pyramid project using "pcreate".</p>"""
            )
        )
        self.createProjectAct.triggered.connect(self.__createProject)
        self.actions.append(self.createProjectAct)

        ##############################
        ## run actions below        ##
        ##############################

        self.runServerAct = EricAction(
            self.tr("Run Server"),
            self.tr("Run &Server"),
            0,
            0,
            self,
            "pyramid_run_server",
        )
        self.runServerAct.setStatusTip(self.tr("Starts the Pyramid Web server"))
        self.runServerAct.setWhatsThis(
            self.tr(
                """<b>Run Server</b>"""
                """<p>Starts the Pyramid Web server using"""
                """ "pserve --reload development.ini".</p>"""
            )
        )
        self.runServerAct.triggered.connect(self.__runServer)
        self.actions.append(self.runServerAct)

        self.runBrowserAct = EricAction(
            self.tr("Run Web-Browser"),
            self.tr("Run &Web-Browser"),
            0,
            0,
            self,
            "pyramid_run_browser",
        )
        self.runBrowserAct.setStatusTip(
            self.tr(
                "Starts the default Web-Browser with the URL of the Pyramid Web"
                " server"
            )
        )
        self.runBrowserAct.setWhatsThis(
            self.tr(
                """<b>Run Web-Browser</b>"""
                """<p>Starts the default Web-Browser with the URL of the """
                """Pyramid Web server.</p>"""
            )
        )
        self.runBrowserAct.triggered.connect(self.__runBrowser)
        self.actions.append(self.runBrowserAct)

        self.runPythonShellAct = EricAction(
            self.tr("Start Pyramid Python Console"),
            self.tr("Start Pyramid &Python Console"),
            0,
            0,
            self,
            "pyramid_python_console",
        )
        self.runPythonShellAct.setStatusTip(
            self.tr("Starts an interactive Python interpreter")
        )
        self.runPythonShellAct.setWhatsThis(
            self.tr(
                """<b>Start Pyramid Python Console</b>"""
                """<p>Starts an interactive Python interpreter.</p>"""
            )
        )
        self.runPythonShellAct.triggered.connect(self.__runPythonShell)
        self.actions.append(self.runPythonShellAct)

        ###############################
        ## show actions below        ##
        ###############################

        self.showViewsAct = EricAction(
            self.tr("Show Matching Views"),
            self.tr("Show Matching &Views"),
            0,
            0,
            self,
            "pyramid_show_views",
        )
        self.showViewsAct.setStatusTip(self.tr("Show views matching a given URL"))
        self.showViewsAct.setWhatsThis(
            self.tr(
                """<b>Show Matching Views</b>"""
                """<p>Show views matching a given URL.</p>"""
            )
        )
        self.showViewsAct.triggered.connect(self.__showMatchingViews)
        self.actions.append(self.showViewsAct)

        self.showRoutesAct = EricAction(
            self.tr("Show Routes"),
            self.tr("Show &Routes"),
            0,
            0,
            self,
            "pyramid_show_routes",
        )
        self.showRoutesAct.setStatusTip(
            self.tr("Show all URL dispatch routes used by a Pyramid application")
        )
        self.showRoutesAct.setWhatsThis(
            self.tr(
                """<b>Show Routes</b>"""
                """<p>Show all URL dispatch routes used by a Pyramid application"""
                """ in the order in which they are evaluated.</p>"""
            )
        )
        self.showRoutesAct.triggered.connect(self.__showRoutes)
        self.actions.append(self.showRoutesAct)

        self.showTweensAct = EricAction(
            self.tr("Show Tween Objects"),
            self.tr("Show &Tween Objects"),
            0,
            0,
            self,
            "pyramid_show_routes",
        )
        self.showTweensAct.setStatusTip(
            self.tr(
                "Show all implicit and explicit tween objects used by a Pyramid"
                " application"
            )
        )
        self.showTweensAct.setWhatsThis(
            self.tr(
                """<b>Show Tween Objects</b>"""
                """<p>Show all implicit and explicit tween objects used by a"""
                """ Pyramid application.</p>"""
            )
        )
        self.showTweensAct.triggered.connect(self.__showTweens)
        self.actions.append(self.showTweensAct)

        ##################################
        ## distribution actions below   ##
        ##################################

        self.buildDistroAct = EricAction(
            self.tr("Build Distribution"),
            self.tr("Build &Distribution"),
            0,
            0,
            self,
            "pyramid_build_distribution",
        )
        self.buildDistroAct.setStatusTip(
            self.tr("Builds a distribution file for the Pyramid project")
        )
        self.buildDistroAct.setWhatsThis(
            self.tr(
                """<b>Build Distribution</b>"""
                """<p>Builds a distribution file for the Pyramid project using"""
                """ "python setup.py sdist".</p>"""
            )
        )
        self.buildDistroAct.triggered.connect(self.__buildDistribution)
        self.actions.append(self.buildDistroAct)

        ##################################
        ## documentation action below   ##
        ##################################

        self.documentationAct = EricAction(
            self.tr("Documentation"),
            self.tr("D&ocumentation"),
            0,
            0,
            self,
            "pyramid_documentation",
        )
        self.documentationAct.setStatusTip(
            self.tr("Shows the help viewer with the Pyramid documentation")
        )
        self.documentationAct.setWhatsThis(
            self.tr(
                """<b>Documentation</b>"""
                """<p>Shows the help viewer with the Pyramid documentation.</p>"""
            )
        )
        self.documentationAct.triggered.connect(self.__showDocumentation)
        self.actions.append(self.documentationAct)

        ##############################
        ## about action below       ##
        ##############################

        self.aboutPyramidAct = EricAction(
            self.tr("About Pyramid"),
            self.tr("About P&yramid"),
            0,
            0,
            self,
            "pyramid_about",
        )
        self.aboutPyramidAct.setStatusTip(
            self.tr("Shows some information about Pyramid")
        )
        self.aboutPyramidAct.setWhatsThis(
            self.tr(
                """<b>About Pyramid</b>"""
                """<p>Shows some information about Pyramid.</p>"""
            )
        )
        self.aboutPyramidAct.triggered.connect(self.__pyramidInfo)
        self.actions.append(self.aboutPyramidAct)

        self.__initDatabaseActions()

        self.__setCurrentProject(None)

    def __initDatabaseActions(self):
        """
        Private method to initialize the database related actions.
        """
        self.initializeDbAct = EricAction(
            self.tr("Initialize Database"),
            self.tr("Initialize &Database"),
            0,
            0,
            self,
            "pyramid_initialize_database",
        )
        self.initializeDbAct.setStatusTip(
            self.tr(
                "Initializes (or re-initializes) the database of the current"
                " Pyramid project"
            )
        )
        self.initializeDbAct.setWhatsThis(
            self.tr(
                """<b>Initialize Database</b>"""
                """<p>Initializes (or re-initializes) the database of the"""
                """ current Pyramid project.</p>"""
            )
        )
        self.initializeDbAct.triggered.connect(self.__initializeDatabase)
        self.actions.append(self.initializeDbAct)

        #########################################################
        ## action to create a new database migration
        #########################################################

        self.migrateCreateAct = EricAction(
            self.tr("Create Migration"),
            self.tr("&Create Migration"),
            0,
            0,
            self,
            "flask_create_migration",
        )
        self.migrateCreateAct.setStatusTip(
            self.tr("Create a new migration for the current database")
        )
        self.migrateCreateAct.setWhatsThis(
            self.tr(
                """<b>Create Migration</b>"""
                """<p>Creates a new migration for the current database"""
                """ and stores it  in the configured migrations directory.</p>"""
            )
        )
        self.migrateCreateAct.triggered.connect(self.__createMigration)
        self.actions.append(self.migrateCreateAct)

        #########################################################
        ## action to up- and downgrade a databse
        #########################################################

        self.upgradeDatabaseAct = EricAction(
            self.tr("Upgrade Database"),
            self.tr("&Upgrade Database"),
            0,
            0,
            self,
            "flask_upgrade_database",
        )
        self.upgradeDatabaseAct.setStatusTip(
            self.tr("Upgrade the database to the current migration")
        )
        self.upgradeDatabaseAct.setWhatsThis(
            self.tr(
                """<b>Upgrade Database</b>"""
                """<p>Upgrades the database to the current migration.</p>"""
            )
        )
        self.upgradeDatabaseAct.triggered.connect(self.upgradeDatabase)
        self.actions.append(self.upgradeDatabaseAct)

        self.downgradeDatabaseAct = EricAction(
            self.tr("Downgrade Database"),
            self.tr("&Downgrade Database"),
            0,
            0,
            self,
            "flask_downgrade_database",
        )
        self.downgradeDatabaseAct.setStatusTip(
            self.tr("Downgrade the database to the previous version")
        )
        self.downgradeDatabaseAct.setWhatsThis(
            self.tr(
                """<b>Downgrade Database</b>"""
                """<p>Downgrades the database to the previous version.</p>"""
            )
        )
        self.downgradeDatabaseAct.triggered.connect(self.downgradeDatabase)
        self.actions.append(self.downgradeDatabaseAct)

        #########################################################
        ## actions to show migrations history information
        #########################################################

        self.migrationSummaryAct = EricAction(
            self.tr("Show Migrations Summary"),
            self.tr("Show Migrations &Summary"),
            0,
            0,
            self,
            "flask_show_migrations_summary",
        )
        self.migrationSummaryAct.setStatusTip(
            self.tr("Show a summary of the created database migrations")
        )
        self.migrationSummaryAct.setWhatsThis(
            self.tr(
                """<b>Show Migrations Summary</b>"""
                """<p>Shows a summary list of the created database"""
                """ migrations.</p>"""
            )
        )
        self.migrationSummaryAct.triggered.connect(self.__showMigrationsSummary)
        self.actions.append(self.migrationSummaryAct)

        self.migrationHistoryAct = EricAction(
            self.tr("Show Migrations History"),
            self.tr("Show Migrations &History"),
            0,
            0,
            self,
            "flask_show_migrations_history",
        )
        self.migrationHistoryAct.setStatusTip(
            self.tr("Show the full history of the created database migrations")
        )
        self.migrationHistoryAct.setWhatsThis(
            self.tr(
                """<b>Show Migrations History</b>"""
                """<p>Shows the full history of the created database"""
                """ migrations.</p>"""
            )
        )
        self.migrationHistoryAct.triggered.connect(self.__showMigrationsHistory)
        self.actions.append(self.migrationHistoryAct)

    def initMenu(self):
        """
        Public slot to initialize the Pyramid menu.

        @return the menu generated
        @rtype QMenu
        """
        self.__menus = {}  # clear menus references

        # Database menu
        dbMenu = QMenu(self.tr("Database"))
        dbMenu.setTearOffEnabled(True)

        dbMenu.addAction(self.initializeDbAct)
        dbMenu.addSeparator()
        dbMenu.addAction(self.migrateCreateAct)
        dbMenu.addSeparator()
        dbMenu.addAction(self.upgradeDatabaseAct)
        dbMenu.addAction(self.downgradeDatabaseAct)
        dbMenu.addSeparator()
        dbMenu.addAction(self.migrationSummaryAct)
        dbMenu.addAction(self.migrationHistoryAct)

        # main Pyramid menu
        menu = QMenu(self.tr("P&yramid"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.selectProjectAct)
        menu.addSeparator()
        menu.addAction(self.runServerAct)
        menu.addAction(self.runBrowserAct)
        menu.addSeparator()
        menu.addAction(self.runPythonShellAct)
        menu.addSeparator()
        menu.addAction(self.createProjectAct)
        menu.addSeparator()
        menu.addMenu(dbMenu)
        menu.addSeparator()
        menu.addAction(self.showViewsAct)
        menu.addAction(self.showRoutesAct)
        menu.addAction(self.showTweensAct)
        menu.addSeparator()
        menu.addAction(self.buildDistroAct)
        menu.addSeparator()
        menu.addAction(self.documentationAct)
        menu.addSeparator()
        menu.addAction(self.aboutPyramidAct)

        self.__menus["main"] = menu
        self.__menus["database"] = dbMenu

        self.__setCurrentProject(None)

        return menu

    def getMenu(self, name):
        """
        Public method to get a reference to the requested menu.

        @param name name of the menu
        @type str
        @return reference to the menu or None, if no menu with the given
            name exists
        @rtype QMenu
        """
        if name in self.__menus:
            return self.__menus[name]
        else:
            return None

    def getMenuNames(self):
        """
        Public method to get the names of all menus.

        @return menu names
        @rtype list of str
        """
        return list(self.__menus.keys())

    def registerOpenHook(self):
        """
        Public method to register the open hook to open a translations file
        in a translations editor.
        """
        if self.__hooksInstalled:
            editor = self.__plugin.getPreferences("TranslationsEditor")
            if editor:
                self.__translationsBrowser.addHookMethodAndMenuEntry(
                    "open",
                    self.openPOEditor,
                    self.tr("Open with {0}").format(os.path.basename(editor)),
                )
            else:
                self.__translationsBrowser.removeHookMethod("open")

    def projectOpenedHooks(self):
        """
        Public method to add our hook methods.
        """
        if self.__ericProject.getProjectType() == "Pyramid":
            self.__formsBrowser = (
                ericApp().getObject("ProjectBrowser").getProjectBrowser("forms")
            )
            self.__formsBrowser.addHookMethodAndMenuEntry(
                "newForm", self.newForm, self.tr("New template...")
            )

            self.__ericProject.projectLanguageAddedByCode.connect(
                self.__projectLanguageAdded
            )
            self.__translationsBrowser = (
                ericApp().getObject("ProjectBrowser").getProjectBrowser("translations")
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "extractMessages", self.extractMessages, self.tr("Extract Messages")
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "releaseAll", self.compileCatalogs, self.tr("Compile All Catalogs")
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "releaseSelected",
                self.compileSelectedCatalogs,
                self.tr("Compile Selected Catalogs"),
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "generateAll", self.updateCatalogs, self.tr("Update All Catalogs")
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "generateSelected",
                self.updateSelectedCatalogs,
                self.tr("Update Selected Catalogs"),
            )

            self.__hooksInstalled = True

            self.registerOpenHook()

    def projectClosedHooks(self):
        """
        Public method to remove our hook methods.
        """
        if self.__hooksInstalled:
            self.__formsBrowser.removeHookMethod("newForm")
            self.__formsBrowser = None

            self.__ericProject.projectLanguageAddedByCode.disconnect(
                self.__projectLanguageAdded
            )
            self.__translationsBrowser.removeHookMethod("extractMessages")
            self.__translationsBrowser.removeHookMethod("releaseAll")
            self.__translationsBrowser.removeHookMethod("releaseSelected")
            self.__translationsBrowser.removeHookMethod("generateAll")
            self.__translationsBrowser.removeHookMethod("generateSelected")
            self.__translationsBrowser.removeHookMethod("open")
            self.__translationsBrowser = None

        self.__hooksInstalled = False

    def newForm(self, path):
        """
        Public method to create a new form.

        @param path full directory path for the new form file
        @type str
        """
        from .FormSelectionDialog import FormSelectionDialog

        dlg = FormSelectionDialog()
        if dlg.exec() == QDialog.DialogCode.Accepted:
            template = dlg.getTemplateText()

            fileFilters = self.tr(
                "Chameleon Templates (*.pt);;"
                "Chameleon Text Templates (*.txt);;"
                "Mako Templates (*.mako);;"
                "Mako Templates (*.mak);;"
                "HTML Files (*.html);;"
                "HTML Files (*.htm);;"
                "All Files (*)"
            )
            fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
                self.__ui,
                self.tr("New Form"),
                path,
                fileFilters,
                None,
                EricFileDialog.DontConfirmOverwrite,
            )
            if fname:
                ext = QFileInfo(fname).suffix()
                if not ext:
                    ex = selectedFilter.split("(*")[1].split(")")[0]
                    if ex:
                        fname += ex

                if os.path.exists(fname):
                    res = EricMessageBox.yesNo(
                        self.__ui,
                        self.tr("New Form"),
                        self.tr("""The file already exists! Overwrite""" """ it?"""),
                        icon=EricMessageBox.Warning,
                    )
                    if not res:
                        # user selected to not overwrite
                        return

                try:
                    with open(fname, "w", encoding="utf-8") as f:
                        f.write(template)
                except OSError as e:
                    EricMessageBox.critical(
                        self.__ui,
                        self.tr("New Form"),
                        self.tr(
                            "<p>The new form file <b>{0}</b> could"
                            " not be created.<br/> Problem: {1}</p>"
                        ).format(fname, e),
                    )
                    return

                self.__ericProject.appendFile(fname)
                self.__formsBrowser.sourceFile.emit(fname)

    ##################################################################
    ## methods below implement general functionality
    ##################################################################

    def projectClosed(self):
        """
        Public method to handle the closing of a project.
        """
        if self.__serverProc is not None:
            self.__serverProcFinished()
        self.__setCurrentProject(None)

        for dlg in (self.__migrationSummaryDialog,):
            if dlg is not None:
                dlg.close()

    def __getExecutablePaths(self, file):
        """
        Private method to build all full paths of an executable file from
        the environment.

        @param file filename of the executable
        @type str
        @return list of full executable names, if the executable file is
            accessible via the searchpath defined by the PATH environment
            variable, or an empty list otherwise.
        @rtype list of str
        """
        paths = []

        if os.path.isabs(file):
            if os.access(file, os.X_OK):
                return [file]
            else:
                return []

        cur_path = os.path.join(os.curdir, file)
        if os.path.exists(cur_path) and os.access(cur_path, os.X_OK):
            paths.append(cur_path)

        path = os.getenv("PATH")

        # environment variable not defined
        if path is not None:
            dirs = path.split(os.pathsep)
            for directory in dirs:
                exe = os.path.join(directory, file)
                if os.access(exe, os.X_OK) and exe not in paths:
                    paths.append(exe)

        return paths

    def supportedPythonVariants(self):
        """
        Public method to get the supported Python variants.

        @return list of supported Python variants
        @rtype list of str
        """
        variants = []
        cmd = "cookiecutter"

        for variant in ["Python3"]:
            virtEnv = self.__getVirtualEnvironment(variant)
            if virtEnv:
                fullCmd = self.getPyramidCommand(cmd, variant)
                if fullCmd != cmd:
                    variants.append(variant)
            else:
                fullCmd = self.getPyramidCommand(cmd, variant)
                if isWindowsPlatform():
                    if fullCmd != cmd:
                        variants.append(variant)
                else:
                    if cmd:
                        try:
                            fullCmds = Utilities.getExecutablePaths(cmd)
                        except AttributeError:
                            fullCmds = self.__getExecutablePaths(cmd)
                        for fullCmd in fullCmds:
                            try:
                                with open(fullCmd, "r", encoding="utf-8") as f:
                                    l0 = f.readline()
                            except OSError:
                                l0 = ""
                            if self.__isSuitableForVariant(variant, l0):
                                variants.append(variant)
                                break

        return variants

    def __isSuitableForVariant(self, variant, line0):
        """
        Private method to test, if a detected command file is suitable for the
        given Python variant.

        @param variant Python variant to test for
        @type str
        @param line0 first line of the executable
        @type str
        @return flag indicating a suitable command
        @rtype bool
        """
        l0 = line0.lower()
        ok = variant.lower() in l0 or "{0}.".format(variant[-1]) in l0
        ok |= "pypy3" in l0

        return ok

    def __getVirtualEnvironment(self, language=""):
        """
        Private method to get the path of the virtual environment.

        @param language Python variant to get the virtual environment
            for (one of '' or 'Python3')
        @type str
        @return path of the virtual environment
        @rtype str
        """
        if not language:
            language = self.__ericProject.getProjectLanguage()
        if self.__virtualEnvManager:
            if language == "Python3":
                venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
            else:
                venvName = ""
            if venvName:
                virtEnv = self.__virtualEnvManager.getVirtualenvDirectory(venvName)
                if not virtEnv:
                    virtEnv = os.path.dirname(
                        self.__virtualEnvManager.getVirtualenvInterpreter(venvName)
                    )
                    if virtEnv.endswith(("Scripts", "bin")):
                        virtEnv = os.path.dirname(virtEnv)
            else:
                virtEnv = ""

        if virtEnv and not os.path.exists(virtEnv):
            virtEnv = ""
        return virtEnv  # __IGNORE_WARNING_M834__

    def __getDebugEnvironment(self, language=""):
        """
        Private method to get the path of the debugger environment.

        @param language Python variant to get the debugger environment
            for (one of '' or 'Python3')
        @type str
        @return path of the debugger environment
        @rtype str
        """
        if not language:
            language = self.__ericProject.getProjectLanguage()
        if self.__virtualEnvManager:
            debugEnv = self.__getVirtualEnvironment(language)
            if not debugEnv:
                if language == "Python3":
                    venvName = Preferences.getDebugger("Python3VirtualEnv")
                else:
                    venvName = ""

                if venvName:
                    return self.__virtualEnvManager.getVirtualenvDirectory(venvName)

        return ""

    def getProjectVirtualEnvironment(self):
        """
        Public method to generate the path of the project virtual environment.

        @return path of the Pyramid project virtual environment
        @rtype str
        """
        return os.path.join(self.projectPath(), "env")

    def getPyramidCommand(self, cmd, language="", virtualEnv=""):
        """
        Public method to build a Pyramid command.

        @param cmd command
        @type str
        @param language Python variant to get the virtual environment
            for (one of '' or 'Python3')
        @type str
        @param virtualEnv path of the project's Python virtual environment
        @type str
        @return full pyramid command
        @rtype str
        """
        if not language:
            language = self.__ericProject.getProjectLanguage()

        if not virtualEnv:
            virtualEnv = self.__getVirtualEnvironment(language)
            if not virtualEnv:
                virtualEnv = self.__getDebugEnvironment(language)
        if isWindowsPlatform():
            fullCmds = [
                os.path.join(virtualEnv, "Scripts", cmd + ".exe"),
                os.path.join(virtualEnv, "bin", cmd + ".exe"),
                cmd,  # fall back to just cmd
            ]
            for cmd in fullCmds:
                if os.path.exists(cmd):
                    break
        else:
            fullCmds = [
                os.path.join(virtualEnv, "bin", cmd),
                os.path.join(virtualEnv, "local", "bin", cmd),
                Utilities.getExecutablePath(cmd),
                cmd,  # fall back to just cmd
            ]
            for cmd in fullCmds:
                if os.path.exists(cmd):
                    break
        return cmd

    def __assemblePyramidCommand(self, cmd, virtualEnv):
        """
        Private method to assemble the full pyramid command for a given virtual
        environment.

        @param cmd command
        @type str
        @param virtualEnv path of the project's Python virtual environment
        @type str
        @return assembled pyramid command
        @rtype str
        """
        return (
            os.path.join(virtualEnv, "Scripts", cmd + ".exe")
            if isWindowsPlatform()
            else os.path.join(virtualEnv, "bin", cmd)
        )

    def getPythonCommand(self):
        """
        Public method to build the Python command.

        @return python command
        @rtype str
        """
        language = self.__ericProject.getProjectLanguage()
        if self.__virtualEnvManager:
            if language == "Python3":
                venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
                if not venvName:
                    # if none configured, use the global one
                    venvName = Preferences.getDebugger("Python3VirtualEnv")
            else:
                venvName = ""
            if venvName:
                return self.__virtualEnvManager.getVirtualenvInterpreter(venvName)

        return ""

    def __pyramidInfo(self):
        """
        Private slot to show some info about Pyramid.
        """
        try:
            version = self.getPyramidVersionString()
        except PyramidNoProjectSelectedError:
            version = self.tr("not available")
        url = "http://www.pylonsproject.org/projects/pyramid/about"

        msgBox = EricMessageBox.EricMessageBox(
            EricMessageBox.Question,
            self.tr("About Pyramid"),
            self.tr(
                "<p>Pyramid is a high-level Python Web framework that"
                " encourages rapid development and clean, pragmatic"
                " design.</p>"
                "<p><table>"
                "<tr><td>Version:</td><td>{0}</td></tr>"
                '<tr><td>URL:</td><td><a href="{1}">'
                "{1}</a></td></tr>"
                "</table></p>"
            ).format(version, url),
            modal=True,
            buttons=EricMessageBox.Ok,
        )
        msgBox.setIconPixmap(
            EricPixmapCache.getPixmap(
                os.path.join(
                    "ProjectPyramid", "icons", "pyramid64-{0}".format(self.__iconSuffix)
                )
            )
        )
        msgBox.exec()

    def getPyramidVersionString(self):
        """
        Public method to get the Pyramid version as a string.

        @return Pyramid version
        @rtype str
        """
        if not self.__pyramidVersion:
            cmd = self.getPyramidCommand(
                "pdistreport", virtualEnv=self.getProjectVirtualEnvironment()
            )
            try:
                output = subprocess.check_output([cmd])  # secok
                outputLines = output.decode().splitlines()
                for line in outputLines:
                    if line.startswith("Pyramid version:"):
                        self.__pyramidVersion = line.rsplit(None, 1)[1]
                        break
            except (OSError, subprocess.CalledProcessError):
                self.__pyramidVersion = ""

        return self.__pyramidVersion

    def getPyramidVersion(self):
        """
        Public method to get the Pyramid version as a tuple.

        @return Pyramid version
        @rtype tuple of int
        """
        pyramidVersionStr = self.getPyramidVersionString()
        pyramidVersionList = []
        if pyramidVersionStr:
            for part in pyramidVersionStr.split("."):
                try:
                    pyramidVersionList.append(int(part))
                except ValueError:
                    pyramidVersionList.append(part)

        return tuple(pyramidVersionList)

    def isSpawningConsole(self, consoleCmd):
        """
        Public method to check, if the given console is a spawning console.

        @param consoleCmd console command
        @type str
        @return tuple of two entries giving an indication, if the console
            is spawning (boolean) and the (possibly) cleaned console command
        @rtype str
        """
        if consoleCmd and consoleCmd[0] == "@":
            return (True, consoleCmd[1:])
        else:
            return (False, consoleCmd)

    def __adjustWorkingDirectory(self, args, wd):
        """
        Private method to adjust the working directory in the arguments list.

        @param args list of arguments to be modified
        @type list of str
        @param wd working directory
        @type str
        """
        if args[0].endswith("konsole") and "--workdir" in args:
            index = args.index("--workdir")
            args[index + 1] = wd
        elif args[0].endswith(("gnome-terminal", "mate-terminal")):
            for index in range(len(args)):
                if args[index].startswith("--working-directory="):
                    args[index] = "--working-directory={0}".format(wd)
                    break

    ##################################################################
    ## slots below implement creation functions
    ##################################################################

    def __createProject(self):
        """
        Private slot to create a new Pyramid project.
        """
        from .CreateParametersDialog import CreateParametersDialog

        dlg = CreateParametersDialog(self.__ui)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            template, version, overwrite, contextData = dlg.getData()

            cmd = self.getPyramidCommand("cookiecutter")
            args = ["--no-input"]
            if overwrite:
                args.append("--overwrite-if-exists")
            if version:
                args += ["--checkout", version]
            args.append(template)
            for context, data in contextData.items():
                args.append("{0}={1}".format(context, data))
            dlg = PyramidDialog(self.tr("Create Pyramid Project"), parent=self.__ui)
            if dlg.startProcess(cmd, args, self.__ericProject.getProjectPath()):
                dlg.exec()
                if dlg.normalExit() and "repo_name" in contextData:
                    # search for files created by cookiecutter and add them
                    # to the project
                    projectPath = os.path.join(
                        self.__ericProject.getProjectPath(), contextData["repo_name"]
                    )
                    for entry in os.walk(projectPath):
                        for fileName in entry[2]:
                            fullName = os.path.join(entry[0], fileName)
                            self.__ericProject.appendFile(fullName)

                    # create the base directory for translations
                    i18nPath = os.path.join(
                        projectPath, contextData["repo_name"].lower(), "i18n"
                    )
                    if not os.path.exists(i18nPath):
                        os.makedirs(i18nPath)
                    self.__ericProject.setDirty(True)

                    combinedOutput = False
                    argsLists = []

                    # 1. create a Python virtual environment for the project
                    argsLists.append([sys.executable, "-m", "venv", "env"])
                    # 2. upgrade packaging tools
                    python = self.__assemblePyramidCommand(
                        "python", os.path.join(projectPath, "env")
                    )
                    argsLists.append(
                        [
                            python,
                            "-m",
                            "pip",
                            "install",
                            "--upgrade",
                            "pip",
                            "setuptools",
                        ]
                    )
                    # 3. install project in editable mode with testing
                    argsLists.append(
                        [python, "-m", "pip", "install", "-e", ".[testing]"]
                    )

                    if (
                        "backend" in contextData
                        and contextData["backend"] == "sqlalchemy"
                    ):
                        # only SQLAlchemy needs initialization of alembic
                        combinedOutput = True

                        # 4. initialize database
                        alembic = self.__assemblePyramidCommand(
                            "alembic", os.path.join(projectPath, "env")
                        )
                        argsLists.append(
                            [
                                alembic,
                                "-c",
                                "development.ini",
                                "revision",
                                "--autogenerate",
                                "--message",
                                "initialized database",
                            ]
                        )
                        # 5. upgrade database to initial version
                        argsLists.append(
                            [alembic, "-c", "development.ini", "upgrade", "head"]
                        )

                    dlg = PyramidDialog(
                        self.tr("Initializing Pyramid Project"),
                        combinedOutput=combinedOutput,
                        parent=self.__ui,
                    )
                    if dlg.startBatchProcesses(argsLists, workingDir=projectPath):
                        dlg.exec()

                    self.__setCurrentProject(contextData["repo_name"])

                    if (
                        "backend" in contextData
                        and contextData["backend"] == "sqlalchemy"
                    ):
                        # add the alembic files created above to the project
                        migrationsPath = self.migrationsPath()
                        for entry in os.walk(migrationsPath):
                            for fileName in entry[2]:
                                fullName = os.path.join(entry[0], fileName)
                                self.__ericProject.appendFile(fullName)

    ##################################################################
    ## methods below implement site related functions
    ##################################################################

    def __findProjects(self):
        """
        Private method to determine the relative path of all Pyramid
        projects (= top level dirs).

        @return list of projects
        @rtype list of str
        """
        projects = []
        ppath = self.__ericProject.getProjectPath()
        for entry in os.listdir(ppath):
            if entry[0] not in "._" and os.path.isdir(os.path.join(ppath, entry)):
                projects.append(entry)
        return sorted(projects)

    def __selectProject(self):
        """
        Private method to select a Pyramid project to work with.
        """
        projects = self.__findProjects()
        if len(projects) == 0:
            project = None
        elif len(projects) == 1:
            project = projects[0]
        else:
            if self.__currentProject is not None:
                try:
                    cur = projects.index(self.__currentProject)
                except ValueError:
                    cur = 0
            else:
                cur = 0
            project, ok = QInputDialog.getItem(
                self.__ui,
                self.tr("Select Pyramid Project"),
                self.tr("Select the Pyramid project to work with."),
                projects,
                cur,
                False,
            )
            if not ok:
                projects = None
        self.__setCurrentProject(project)

    def projectPath(self):
        """
        Public method to calculate the full path of the Pyramid project.

        @return path of the project
        @rtype str
        @exception PyramidNoProjectSelectedError raised, if no project is
            selected
        """
        if self.__currentProject is None:
            self.__selectProject()

        if self.__currentProject is None:
            raise PyramidNoProjectSelectedError
        else:
            return os.path.join(
                self.__ericProject.getProjectPath(), self.__currentProject
            )

    def __setCurrentProject(self, project):
        """
        Private slot to set the current project.

        @param project name of the project
        @type str
        """
        if project is not None and len(project) == 0:
            self.__currentProject = None
        else:
            self.__currentProject = project

        curProject = (
            self.tr("None") if self.__currentProject is None else self.__currentProject
        )
        self.selectProjectAct.setText(
            self.tr("&Current Pyramid Project ({0})").format(curProject)
        )

        if self.__currentProject is None:
            self.__ericProject.setTranslationPattern("")
        else:
            lowerProject = self.__project().lower()
            config = configparser.ConfigParser()
            config.read(os.path.join(self.projectPath(), "setup.cfg"))
            try:
                outputDir = config.get("init_catalog", "output_dir")
            except (configparser.NoOptionError, configparser.NoSectionError):
                outputDir = "{0}/locale".format(lowerProject)
            try:
                domain = config.get("init_catalog", "domain")
            except (configparser.NoOptionError, configparser.NoSectionError):
                domain = lowerProject
            self.__ericProject.setTranslationPattern(
                os.path.join(
                    project,
                    outputDir,
                    "%language%",
                    "LC_MESSAGES",
                    "{0}.po".format(domain),
                )
            )

        if self.__currentProject is None:
            self.initializeDbAct.setEnabled(False)
            with contextlib.suppress(KeyError):
                self.__menus["database"].setEnabled(False)
        else:
            initCmd = self.__getInitDbCommand()
            self.initializeDbAct.setEnabled(os.path.exists(initCmd))

            alembicDir = os.path.join(
                self.projectPath(), self.__currentProject, "alembic", "versions"
            )
            self.__menus["database"].setEnabled(os.path.exists(alembicDir))

        self.runServerAct.setEnabled(project is not None)
        self.runBrowserAct.setEnabled(project is not None)
        self.runPythonShellAct.setEnabled(project is not None)
        self.showViewsAct.setEnabled(project is not None)
        self.showRoutesAct.setEnabled(project is not None)
        self.showTweensAct.setEnabled(project is not None)
        self.buildDistroAct.setEnabled(project is not None)

    def __project(self):
        """
        Private method to get the name of the current Pyramid project.

        @return name of the project
        @rtype str
        @exception PyramidNoProjectSelectedError raised, if no project is
            selected
        """
        if self.__currentProject is None:
            self.__selectProject()

        if self.__currentProject is None:
            raise PyramidNoProjectSelectedError
        else:
            return self.__currentProject

    ##################################################################
    ## slots below implement run functions
    ##################################################################

    def __runServer(self):
        """
        Private slot to start the Pyramid Web server.
        """
        consoleCmd = self.isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand")
        )[1]
        if consoleCmd:
            try:
                projectPath = self.projectPath()
            except PyramidNoProjectSelectedError:
                EricMessageBox.warning(
                    self.__ui,
                    self.tr("Run Server"),
                    self.tr(
                        "No current Pyramid project selected or no"
                        " Pyramid project created yet. Aborting..."
                    ),
                )
                return

            args = Utilities.parseOptionString(consoleCmd)
            args[0] = Utilities.getExecutablePath(args[0])
            args.append(
                self.getPyramidCommand(
                    "pserve", virtualEnv=self.getProjectVirtualEnvironment()
                )
            )
            args.append("--reload")
            args.append(os.path.join(projectPath, "development.ini"))

            if isWindowsPlatform():
                serverProcStarted, pid = QProcess.startDetached(
                    args[0], args[1:], projectPath
                )
            else:
                if self.__serverProc is not None:
                    self.__serverProcFinished()

                self.__serverProc = QProcess()
                self.__serverProc.finished.connect(self.__serverProcFinished)
                self.__serverProc.setWorkingDirectory(projectPath)
                self.__serverProc.start(args[0], args[1:])
                serverProcStarted = self.__serverProc.waitForStarted()
            if not serverProcStarted:
                EricMessageBox.critical(
                    self.__ui,
                    self.tr("Process Generation Error"),
                    self.tr("The Pyramid server could not be started."),
                )

    def __serverProcFinished(self):
        """
        Private slot connected to the finished signal.
        """
        if (
            self.__serverProc is not None
            and self.__serverProc.state() != QProcess.ProcessState.NotRunning
        ):
            self.__serverProc.terminate()
            QTimer.singleShot(2000, self.__serverProc.kill)
            self.__serverProc.waitForFinished(3000)
        self.__serverProc = None

    def __runBrowser(self):
        """
        Private slot to start the default web browser with the server URL.
        """
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                self.tr("Run Web-Browser"),
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        config = configparser.ConfigParser()
        config.read(os.path.join(projectPath, "development.ini"))
        try:
            listen = config.get("server:main", "listen")
        except (configparser.NoOptionError, configparser.NoSectionError):
            listen = "localhost:6543"
        url = "http://{0}".format(listen)
        if self.__plugin.getPreferences("UseExternalBrowser"):
            res = QDesktopServices.openUrl(QUrl(url))
            if not res:
                EricMessageBox.critical(
                    self.__ui,
                    self.tr("Run Web-Browser"),
                    self.tr(
                        "Could not start the web-browser for the URL" ' "{0}".'
                    ).format(url.toString()),
                )
        else:
            self.__ui.launchHelpViewer(url)

    def __runPythonShell(self):
        """
        Private slot to start a Python console for a Pyramid project.
        """
        consoleCmd = self.isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand")
        )[1]
        if consoleCmd:
            try:
                projectPath = self.projectPath()
            except PyramidNoProjectSelectedError:
                EricMessageBox.warning(
                    self.__ui,
                    self.tr("Start Pyramid Python Console"),
                    self.tr(
                        "No current Pyramid project selected or no"
                        " Pyramid project created yet. Aborting..."
                    ),
                )
                return

            args = Utilities.parseOptionString(consoleCmd)
            args[0] = Utilities.getExecutablePath(args[0])
            args.append(
                self.getPyramidCommand(
                    "pshell", virtualEnv=self.getProjectVirtualEnvironment()
                )
            )
            consoleType = self.__plugin.getPreferences("Python3ConsoleType")
            args.append("--python-shell={0}".format(consoleType))
            args.append(os.path.join(projectPath, "development.ini"))

            self.__adjustWorkingDirectory(args, projectPath)
            started, pid = QProcess.startDetached(args[0], args[1:], projectPath)
            if not started:
                EricMessageBox.critical(
                    self.__ui,
                    self.tr("Process Generation Error"),
                    self.tr("The Pyramid Shell process could not be" " started."),
                )

    ##################################################################
    ## slots below implement distribution functions
    ##################################################################

    def __buildDistribution(self):
        """
        Private slot to build a distribution file for the current Pyramid
        project.
        """
        title = self.tr("Build Distribution File")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        from .DistributionTypeSelectionDialog import DistributionTypeSelectionDialog

        dlg = DistributionTypeSelectionDialog(self, projectPath, self.__ui)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            formats = dlg.getFormats()
            cmd = self.getPyramidCommand(
                "python", virtualEnv=self.getProjectVirtualEnvironment()
            )
            args = []
            args.append("setup.py")
            args.append("sdist")
            if formats:
                args.append("--formats={0}".format(",".join(formats)))

            dia = PyramidDialog(
                title,
                msgSuccess=self.tr("Python distribution file built" " successfully."),
            )
            res = dia.startProcess(cmd, args, projectPath)
            if res:
                dia.exec()

    ##################################################################
    ## slots below implement various debugging functions
    ##################################################################

    def __showMatchingViews(self):
        """
        Private slot showing all views that would match a given URL.
        """
        title = self.tr("Show Matching Views")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        url, ok = QInputDialog.getText(
            self.__ui,
            self.tr("Show Matching Views"),
            self.tr("Enter the URL to be matched:"),
            QLineEdit.EchoMode.Normal,
            "/",
        )
        if not ok or url == "":
            return

        cmd = self.getPyramidCommand(
            "pviews", virtualEnv=self.getProjectVirtualEnvironment()
        )
        args = []
        args.append("development.ini")
        args.append(url)

        dia = PyramidDialog(title, fixed=True, linewrap=False)
        res = dia.startProcess(cmd, args, projectPath)
        if res:
            dia.exec()

    def __showRoutes(self):
        """
        Private slot showing all URL dispatch routes.
        """
        title = self.tr("Show Routes")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        from .PyramidRoutesDialog import PyramidRoutesDialog

        dia = PyramidRoutesDialog(self)
        res = dia.start(projectPath)
        if res:
            dia.exec()

    def __showTweens(self):
        """
        Private slot showing all implicit and explicit tween objects.
        """
        title = self.tr("Show Tween Objects")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        cmd = self.getPyramidCommand(
            "ptweens", virtualEnv=self.getProjectVirtualEnvironment()
        )
        args = []
        args.append("development.ini")

        dia = PyramidDialog(title, fixed=True, linewrap=False)
        res = dia.startProcess(cmd, args, projectPath)
        if res:
            dia.exec()

    ##################################################################
    ## slots below implement documentation functions
    ##################################################################

    def __showDocumentation(self):
        """
        Private slot to show the helpviewer with the Pyramid documentation.
        """
        page = self.__plugin.getPreferences("PyramidDocUrl")
        self.__ui.launchHelpViewer(page)

    ##################################################################
    ## slots below implement translation functions
    ##################################################################

    def __getLocale(self, filename):
        """
        Private method to extract the locale out of a file name.

        @param filename name of the file used for extraction
        @type str
        @return extracted locale or None
        @rtype str
        """
        if self.__ericProject.getTranslationPattern():
            # On Windows, path typically contains backslashes. This leads
            # to an invalid search pattern '...\(' because the opening bracket
            # will be escaped.
            pattern = self.__ericProject.getTranslationPattern()
            pattern = os.path.normpath(pattern)
            pattern = pattern.replace("%language%", "(.*?)")
            pattern = pattern.replace("\\", "\\\\")
            match = re.search(pattern, filename)
            if match is not None:
                return match.group(1)

        return None

    def __normalizeList(self, filenames):
        """
        Private method to normalize a list of file names.

        @param filenames list of file names to normalize
        @type list of str
        @return normalized file names
        @rtype list of str
        """
        nfilenames = []
        for filename in filenames:
            if filename.endswith(".mo"):
                filename = filename.replace(".mo", ".po")
            if filename not in nfilenames:
                nfilenames.append(filename)

        return nfilenames

    def __projectFilteredList(self, filenames):
        """
        Private method to filter a list of file names by Pyramid project.

        @param filenames list of file names to be filtered
        @type list of str
        @return file names belonging to the current site
        @rtype list of str
        """
        project = self.__project()
        nfilenames = []
        for filename in filenames:
            if filename.startswith(project + os.sep):
                nfilenames.append(filename)

        return nfilenames

    def extractMessages(self):
        """
        Public method to extract the messages catalog template file.
        """
        title = self.tr("Extract messages")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        config = configparser.ConfigParser()
        config.read(os.path.join(projectPath, "setup.cfg"))
        try:
            potFile = config.get("extract_messages", "output_file")
        except configparser.NoSectionError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    'No setup.cfg found or no "extract_messages"'
                    " section found in setup.cfg."
                ),
            )
            return
        except configparser.NoOptionError:
            EricMessageBox.warning(
                self.__ui, title, self.tr('No "output_file" option found in setup.cfg.')
            )
            return

        with contextlib.suppress(OSError):
            path = os.path.join(projectPath, os.path.dirname(potFile))
            os.makedirs(path)

        cmd = self.getPythonCommand()
        args = []
        args.append("setup.py")
        args.append("extract_messages")

        dia = PyramidDialog(
            title, msgSuccess=self.tr("\nMessages extracted successfully.")
        )
        res = dia.startProcess(cmd, args, projectPath)
        if res:
            dia.exec()
            self.__ericProject.appendFile(os.path.join(projectPath, potFile))

    def __projectLanguageAdded(self, code):
        """
        Private slot handling the addition of a new language.

        @param code language code of the new language
        @type str
        """
        title = self.tr("Initializing message catalog for '{0}'").format(code)
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        cmd = self.getPythonCommand()
        args = []
        args.append("setup.py")
        args.append("init_catalog")
        args.append("-l")
        args.append(code)

        dia = PyramidDialog(
            title, msgSuccess=self.tr("\nMessage catalog initialized" " successfully.")
        )
        res = dia.startProcess(cmd, args, projectPath)
        if res:
            dia.exec()

            langFile = self.__ericProject.getTranslationPattern().replace(
                "%language%", code
            )
            self.__ericProject.appendFile(langFile)

    def compileCatalogs(self, filenames):
        """
        Public method to compile the message catalogs.

        @param filenames list of filenames (not used)
        @type list of str
        """
        title = self.tr("Compiling message catalogs")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        cmd = self.getPythonCommand()
        args = []
        args.append("setup.py")
        args.append("compile_catalog")

        dia = PyramidDialog(
            title, msgSuccess=self.tr("\nMessage catalogs compiled" " successfully.")
        )
        res = dia.startProcess(cmd, args, projectPath)
        if res:
            dia.exec()

            for entry in os.walk(projectPath):
                for fileName in entry[2]:
                    fullName = os.path.join(entry[0], fileName)
                    if fullName.endswith(".mo"):
                        self.__ericProject.appendFile(fullName)

    def compileSelectedCatalogs(self, filenames):
        """
        Public method to update the message catalogs.

        @param filenames list of file names
        @type list of str
        """
        title = self.tr("Compiling message catalogs")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        argsLists = []

        for filename in self.__normalizeList(self.__projectFilteredList(filenames)):
            locale = self.__getLocale(filename)
            if locale:
                args = []
                args.append(self.getPythonCommand())
                args.append("setup.py")
                args.append("compile_catalog")
                args.append("-l")
                args.append(locale)
                argsLists.append(args)

        if len(argsLists) == 0:
            EricMessageBox.warning(
                self.__ui, title, self.tr("No locales detected. Aborting...")
            )
            return

        dia = PyramidDialog(
            title, msgSuccess=self.tr("\nMessage catalogs compiled" " successfully.")
        )
        res = dia.startBatchProcesses(argsLists, projectPath)
        if res:
            dia.exec()

            for entry in os.walk(self.__sitePath()):
                for fileName in entry[2]:
                    fullName = os.path.join(entry[0], fileName)
                    if fullName.endswith(".mo"):
                        self.__ericProject.appendFile(fullName)

    def updateCatalogs(self, filenames):
        """
        Public method to update the message catalogs.

        @param filenames list of filenames (not used)
        @type list of str
        """
        title = self.tr("Updating message catalogs")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        cmd = self.getPythonCommand()
        args = []
        args.append("setup.py")
        args.append("update_catalog")

        dia = PyramidDialog(
            title, msgSuccess=self.tr("\nMessage catalogs updated successfully.")
        )
        res = dia.startProcess(cmd, args, projectPath)
        if res:
            dia.exec()

    def updateSelectedCatalogs(self, filenames):
        """
        Public method to update the message catalogs.

        @param filenames list of filenames
        @type list of str
        """
        title = self.tr("Updating message catalogs")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        argsLists = []

        for filename in self.__normalizeList(self.__projectFilteredList(filenames)):
            locale = self.__getLocale(filename)
            if locale:
                args = []
                args.append(self.getPythonCommand())
                args.append("setup.py")
                args.append("update_catalog")
                args.append("-l")
                args.append(locale)
                argsLists.append(args)

        if len(argsLists) == 0:
            EricMessageBox.warning(
                self.__ui, title, self.tr("No locales detected. Aborting...")
            )
            return

        dia = PyramidDialog(
            title, msgSuccess=self.tr("\nMessage catalogs updated successfully.")
        )
        res = dia.startBatchProcesses(argsLists, projectPath)
        if res:
            dia.exec()

    def openPOEditor(self, poFile):
        """
        Public method to edit the given file in an external .po editor.

        @param poFile name of the .po file
        @type str
        """
        editor = self.__plugin.getPreferences("TranslationsEditor")
        if poFile.endswith(".po") and editor:
            try:
                wd = self.projectPath()
            except PyramidNoProjectSelectedError:
                wd = ""
            started, pid = QProcess.startDetached(editor, [poFile], wd)
            if not started:
                EricMessageBox.critical(
                    None,
                    self.tr("Process Generation Error"),
                    self.tr(
                        "The translations editor process ({0}) could" " not be started."
                    ).format(os.path.basename(editor)),
                )

    #######################################################################
    ## database related methods and slots below
    #######################################################################

    def getAlembicCommand(self):
        """
        Public method to get the path to the alembic executable of the current
        Pyramid project.

        @return path to the alembic executable
        @rtype str
        """
        return self.getPyramidCommand(
            "alembic", virtualEnv=self.getProjectVirtualEnvironment()
        )

    def migrationsPath(self):
        """
        Public method to get the path to the migrations directory of the
        current Pyramid project.

        @return pathof the directory containing the migrations
        @rtype str
        """
        return os.path.join(
            self.projectPath(), self.__currentProject, "alembic", "versions"
        )

    def __getInitDbCommand(self):
        """
        Private method to create the path to the initialization script.

        @return path to the initialization script
        @rtype str
        """
        try:
            cmd = "initialize_{0}_db".format(self.__project())
            return self.getPyramidCommand(
                cmd, virtualEnv=self.getProjectVirtualEnvironment()
            )
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                self.tr("Initialize Database"),
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return ""

    @pyqtSlot()
    def __initializeDatabase(self):
        """
        Private slot to initialize the database of the Pyramid project.
        """
        title = self.tr("Initialize Database")
        try:
            projectPath = self.projectPath()
        except PyramidNoProjectSelectedError:
            EricMessageBox.warning(
                self.__ui,
                title,
                self.tr(
                    "No current Pyramid project selected or no Pyramid"
                    " project created yet. Aborting..."
                ),
            )
            return

        cmd = self.__getInitDbCommand()
        args = []
        args.append("development.ini")

        dia = PyramidDialog(
            title, msgSuccess=self.tr("Database initialized successfully.")
        )
        res = dia.startProcess(cmd, args, projectPath)
        if res:
            dia.exec()

    @pyqtSlot()
    def __createMigration(self):
        """
        Private slot to create a new database migration.
        """
        title = self.tr("Create Migration")
        projectPath = self.projectPath()
        migrations = self.migrationsPath()

        message, ok = QInputDialog.getText(
            None,
            title,
            self.tr("Enter a short message for the migration:"),
            QLineEdit.EchoMode.Normal,
        )
        if ok:
            args = ["-c", "development.ini", "revision", "--autogenerate"]
            if migrations:
                args += ["--version-path", migrations]
            if message:
                args += ["--message", message]

            dlg = PyramidDialog(
                title,
                msgSuccess=self.tr("\nMigration created successfully."),
                linewrap=False,
                combinedOutput=True,
                parent=self.__ui,
            )
            if dlg.startProcess(self.getAlembicCommand(), args, workingDir=projectPath):
                dlg.exec()
                if dlg.normalExit():
                    versionsPattern = os.path.join(migrations, "*.py")
                    for fileName in glob.iglob(versionsPattern):
                        self.__ericProject.appendFile(fileName)

    @pyqtSlot()
    def upgradeDatabase(self, revision=None):
        """
        Public slot to upgrade the database to the head or a given version.

        @param revision migration revision to upgrade to
        @type str
        """
        title = self.tr("Upgrade Database")
        projectPath = self.projectPath()

        args = ["-c", "development.ini", "upgrade"]
        if revision:
            args.append(revision)
        else:
            args.append("head")

        dlg = PyramidDialog(
            title,
            msgSuccess=self.tr("\nDatabase upgraded successfully."),
            linewrap=False,
            combinedOutput=True,
            parent=self.__ui,
        )
        if dlg.startProcess(self.getAlembicCommand(), args, workingDir=projectPath):
            dlg.exec()

    @pyqtSlot()
    def downgradeDatabase(self, revision=None):
        """
        Public slot to downgrade the database to the previous or a given
        version.

        @param revision migration revision to downgrade to
        @type str
        """
        title = self.tr("Downgrade Database")
        projectPath = self.projectPath()

        args = ["-c", "development.ini", "downgrade"]
        if revision:
            args.append(revision)
        else:
            args.append("-1")

        dlg = PyramidDialog(
            title,
            msgSuccess=self.tr("\nDatabase downgraded successfully."),
            linewrap=False,
            combinedOutput=True,
            parent=self.__ui,
        )
        if dlg.startProcess(self.getAlembicCommand(), args, workingDir=projectPath):
            dlg.exec()

    @pyqtSlot()
    def __showMigrationsSummary(self):
        """
        Private slot to show a migrations history summary.
        """
        from .MigrateSummaryDialog import MigrateSummaryDialog

        if self.__migrationSummaryDialog is None:
            self.__migrationSummaryDialog = MigrateSummaryDialog(self, parent=self.__ui)

        self.__migrationSummaryDialog.showSummary()

    @pyqtSlot()
    def __showMigrationsHistory(self):
        """
        Private slot to show the full migrations history.
        """
        title = self.tr("Migrations History")
        projectPath = self.projectPath()

        args = ["-c", "development.ini", "history", "--indicate-current", "--verbose"]

        dlg = PyramidDialog(
            title, linewrap=False, combinedOutput=True, parent=self.__ui
        )
        if dlg.startProcess(self.getAlembicCommand(), args, workingDir=projectPath):
            dlg.exec()

eric ide

mercurial