Wed, 14 Oct 2020 19:00:12 +0200
Changed calls of exec_() into exec().
# -*- coding: utf-8 -*- # Copyright (c) 2012 - 2020 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the Pyramid project support. """ import os import re import configparser from PyQt5.QtCore import QObject, QFileInfo, QTimer, QUrl from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QMenu, QDialog, QInputDialog, QLineEdit from PyQt5.QtCore import QProcess as QProcessPyQt from E5Gui.E5Application import e5App from E5Gui import E5MessageBox, E5FileDialog from E5Gui.E5Action import E5Action from .PyramidDialog import PyramidDialog import Preferences import Utilities from Globals import isWindowsPlatform import UI.PixmapCache class PyramidNoProjectSelectedException(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=QProcessPyQt.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 (string) @keyparam args list of parameters (list of strings) @keyparam mode access mode (QIODevice.OpenMode) """ if args is None: args = [] if cmd.endswith(('gnome-terminal', 'konsole', 'xfce4-terminal', 'mate-terminal')): if '-e' in args: index = args.index('-e') + 1 cargs = ' '.join(args[index:]) args[index:] = [cargs] super(QProcess, self).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 (string) @keyparam args list of parameters (list of strings) @keyparam path new working directory (string) @return tuple of successful start and process id (boolean, integer) """ if args is None: args = [] if cmd.endswith(('gnome-terminal', 'konsole', 'xfce4-terminal', 'mate-terminal')): if '-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(Project, self).__init__(parent) self.__plugin = plugin self.__iconSuffix = iconSuffix self.__ui = parent self.__e5project = e5App().getObject("Project") try: self.__virtualEnvManager = e5App().getObject("VirtualEnvManager") except KeyError: self.__virtualEnvManager = None self.__hooksInstalled = False self.__menus = {} # dictionary with references to menus self.__serverProc = None self.__pyramidVersion = "" def initActions(self): """ Public method to define the Pyramid actions. """ self.actions = [] self.selectProjectAct = E5Action( 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 = E5Action( 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 = E5Action( 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.runLoggingServerAct = E5Action( self.tr('Run Server with Logging'), self.tr('Run Server with &Logging'), 0, 0, self, 'pyramid_run_logging_server') self.runLoggingServerAct.setStatusTip(self.tr( 'Starts the Pyramid Web server with logging')) self.runLoggingServerAct.setWhatsThis(self.tr( """<b>Run Server with Logging</b>""" """<p>Starts the Pyramid Web server with logging using""" """ "pserve --log-file=server.log --reload development.ini".</p>""" )) self.runLoggingServerAct.triggered.connect(self.__runLoggingServer) self.actions.append(self.runLoggingServerAct) self.runBrowserAct = E5Action( 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 = E5Action( 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) ############################## ## setup actions below ## ############################## self.setupDevelopAct = E5Action( self.tr('Setup Development Environment'), self.tr('Setup &Development Environment'), 0, 0, self, 'pyramid_setup_development') self.setupDevelopAct.setStatusTip(self.tr( 'Setup the Pyramid project in development mode')) self.setupDevelopAct.setWhatsThis(self.tr( """<b>Setup Development Environment</b>""" """<p>Setup the Pyramid project in development mode using""" """ "python setup.py develop".</p>""" )) self.setupDevelopAct.triggered.connect(self.__setupDevelop) self.actions.append(self.setupDevelopAct) ############################### ## database actions below ## ############################### self.initializeDbAct = E5Action( 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) ############################### ## show actions below ## ############################### self.showViewsAct = E5Action( 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 = E5Action( 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 = E5Action( 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 = E5Action( 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 = E5Action( 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 = E5Action( 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.__setCurrentProject(None) def initMenu(self): """ Public slot to initialize the Pyramid menu. @return the menu generated (QMenu) """ self.__menus = {} # clear menus references menu = QMenu(self.tr('P&yramid'), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.selectProjectAct) menu.addSeparator() menu.addAction(self.runServerAct) menu.addAction(self.runLoggingServerAct) menu.addAction(self.runBrowserAct) menu.addSeparator() menu.addAction(self.createProjectAct) menu.addSeparator() menu.addAction(self.setupDevelopAct) menu.addSeparator() menu.addAction(self.initializeDbAct) menu.addSeparator() menu.addAction(self.showViewsAct) menu.addAction(self.showRoutesAct) menu.addAction(self.showTweensAct) menu.addSeparator() menu.addAction(self.runPythonShellAct) menu.addSeparator() menu.addAction(self.buildDistroAct) menu.addSeparator() menu.addAction(self.documentationAct) menu.addSeparator() menu.addAction(self.aboutPyramidAct) self.__menus["main"] = menu return menu def getMenu(self, name): """ Public method to get a reference to the requested menu. @param name name of the menu (string) @return reference to the menu (QMenu) or None, if no menu with the given name exists """ 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 (list of string) """ 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.__e5project.getProjectType() == "Pyramid": self.__formsBrowser = ( e5App().getObject("ProjectBrowser") .getProjectBrowser("forms")) self.__formsBrowser.addHookMethodAndMenuEntry( "newForm", self.newForm, self.tr("New template...")) self.__e5project.projectLanguageAddedByCode.connect( self.__projectLanguageAdded) self.__translationsBrowser = ( e5App().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.__e5project.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 (string) """ from .FormSelectionDialog import FormSelectionDialog dlg = FormSelectionDialog() if dlg.exec() == QDialog.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 = E5FileDialog.getSaveFileNameAndFilter( self.__ui, self.tr("New Form"), path, fileFilters, None, E5FileDialog.Options(E5FileDialog.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 = E5MessageBox.yesNo( self.__ui, self.tr("New Form"), self.tr("""The file already exists! Overwrite""" """ it?"""), icon=E5MessageBox.Warning) if not res: # user selected to not overwrite return try: with open(fname, "w", encoding="utf-8") as f: f.write(template) except IOError as e: E5MessageBox.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.__e5project.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) def __getExecutablePaths(self, file): """ Private method to build all full paths of an executable file from the environment. @param file filename of the executable (string) @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. """ 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): if 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 (list of strings) """ variants = [] cmd = "pcreate" 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 (IOError, 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 (string, one of '' or 'Python3') @return path of the virtual environment (string) """ if not language: language = self.__e5project.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 (string, one of '' or 'Python3') @return path of the debugger environment (string) """ if not language: language = self.__e5project.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 getPyramidCommand(self, cmd, language=""): """ Public method to build a Pyramid command. @param cmd command (string) @param language Python variant to get the virtual environment for (string, one of '' or 'Python3') @return full pyramid command (string) """ if not language: language = self.__e5project.getProjectLanguage() virtualEnv = self.__getVirtualEnvironment(language) if isWindowsPlatform() and 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 getPythonCommand(self): """ Public method to build the Python command. @return python command (string) """ language = self.__e5project.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. """ version = self.getPyramidVersionString() url = "http://www.pylonsproject.org/projects/pyramid/about" msgBox = E5MessageBox.E5MessageBox( E5MessageBox.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=E5MessageBox.Ok) msgBox.setIconPixmap(UI.PixmapCache.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("pcreate") if isWindowsPlatform(): cmd = os.path.join(os.path.dirname(cmd), "pcreate-script.py") try: with open(cmd, 'r', encoding="utf-8") as f: lines = f.read().splitlines() for line in lines: if line.startswith("__requires__"): ## sample: __requires__ = 'pyramid==1.4' vers = line.strip().split()[-1][1:-1].split("==")[1] self.__pyramidVersion = vers except (IOError, OSError): 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 (string) @return tuple of two entries giving an indication, if the console is spawning (boolean) and the (possibly) cleaned console command (string) """ 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) if dlg.exec() == QDialog.Accepted: scaffold, project, overwrite, simulate = dlg.getData() cmd = self.getPyramidCommand("pcreate") args = [] if overwrite: args.append("--overwrite") else: args.append("--interactive") if simulate: args.append("--simulate") args.append("--scaffold={0}".format(scaffold)) args.append(project) dlg = PyramidDialog(self.tr("Create Pyramid Project"), linewrap=False, parent=self.__ui) if dlg.startProcess(cmd, args, self.__e5project.getProjectPath()): dlg.exec() if dlg.normalExit() and not simulate: # search for files created by pcreate and add them to the # project projectPath = os.path.join( self.__e5project.getProjectPath(), project) for entry in os.walk(projectPath): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) self.__e5project.appendFile(fullName) # create the base directory for translations i18nPath = os.path.join( projectPath, project.lower(), "i18n") if not os.path.exists(i18nPath): os.makedirs(i18nPath) self.__e5project.setDirty(True) self.__setCurrentProject(project) ################################################################## ## 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 (list of string) """ projects = [] ppath = self.__e5project.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 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): """ Private method to calculate the full path of the Pyramid project. @exception PyramidNoProjectSelectedException raised, if no project is selected @return path of the project (string) """ if self.__currentProject is None: self.__selectProject() if self.__currentProject is None: raise PyramidNoProjectSelectedException else: return os.path.join(self.__e5project.getProjectPath(), self.__currentProject) def __setCurrentProject(self, project): """ Private slot to set the current project. @param project name of the project (string) """ if project is not None and len(project) == 0: self.__currentProject = None else: self.__currentProject = project if self.__currentProject is None: curProject = self.tr("None") else: curProject = self.__currentProject self.selectProjectAct.setText( self.tr('&Current Pyramid Project ({0})').format(curProject)) if self.__currentProject is None: try: self.__e5project.setTranslationPattern("") except AttributeError: # backward compatibility self.__e5project.pdata["TRANSLATIONPATTERN"] = [] 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 try: self.__e5project.setTranslationPattern( os.path.join(project, outputDir, "%language%", "LC_MESSAGES", "{0}.po".format(domain)) ) except AttributeError: # backward compatibility self.__e5project.pdata["TRANSLATIONPATTERN"] = [ os.path.join(project, outputDir, "%language%", "LC_MESSAGES", "{0}.po".format(domain)) ] if self.__currentProject is None: self.initializeDbAct.setEnabled(False) else: initCmd = self.__getInitDbCommand() self.initializeDbAct.setEnabled(os.path.exists(initCmd)) def __project(self): """ Private method to get the name of the current Pyramid project. @exception PyramidNoProjectSelectedException raised, if no project is selected @return name of the project (string) """ if self.__currentProject is None: self.__selectProject() if self.__currentProject is None: raise PyramidNoProjectSelectedException else: return self.__currentProject ################################################################## ## slots below implement run functions ################################################################## def __runServer(self, logging=False): """ Private slot to start the Pyramid Web server. @param logging flag indicating to enable logging (boolean) """ consoleCmd = self.isSpawningConsole( self.__plugin.getPreferences("ConsoleCommand"))[1] if consoleCmd: try: projectPath = self.__projectPath() except PyramidNoProjectSelectedException: E5MessageBox.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")) if logging: args.append("--log-file=server.log") 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: E5MessageBox.critical( self.__ui, self.tr('Process Generation Error'), self.tr('The Pyramid server could not be started.')) def __runLoggingServer(self): """ Private slot to start the Pyramid Web server with logging. """ self.__runServer(True) def __serverProcFinished(self): """ Private slot connected to the finished signal. """ if ( self.__serverProc is not None and self.__serverProc.state() != QProcess.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 PyramidNoProjectSelectedException: E5MessageBox.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: port = config.get("server:main", "port") except (configparser.NoOptionError, configparser.NoSectionError): port = "8080" url = "http://localhost:{0}".format(port) if self.__plugin.getPreferences("UseExternalBrowser"): res = QDesktopServices.openUrl(QUrl(url)) if not res: E5MessageBox.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 PyramidNoProjectSelectedException: E5MessageBox.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")) 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: E5MessageBox.critical( self.__ui, self.tr('Process Generation Error'), self.tr('The Pyramid Shell process could not be' ' started.')) ################################################################## ## slots below implement setup functions ################################################################## def __setupDevelop(self): """ Private slot to set up the development environment for the current project. """ title = self.tr("Setup Development Environment") try: wd = self.__projectPath() except PyramidNoProjectSelectedException: E5MessageBox.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("develop") dia = PyramidDialog( title, msgSuccess=self.tr("Pyramid development environment setup" " successfully.")) res = dia.startProcess(cmd, args, wd) if res: dia.exec() initCmd = self.__getInitDbCommand() self.initializeDbAct.setEnabled(os.path.exists(initCmd)) ################################################################## ## 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 PyramidNoProjectSelectedException: E5MessageBox.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.Accepted: formats = dlg.getFormats() cmd = self.getPythonCommand() 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 database functions ################################################################## def __getInitDbCommand(self): """ Private method to create the path to the initialization script. @return path to the initialization script (string) """ try: cmd = "initialize_{0}_db".format(self.__project()) return self.getPyramidCommand(cmd) except PyramidNoProjectSelectedException: E5MessageBox.warning( self.__ui, self.tr("Initialize Database"), self.tr('No current Pyramid project selected or no Pyramid' ' project created yet. Aborting...')) return "" def __initializeDatabase(self): """ Private slot to initialize the database of the Pyramid project. """ title = self.tr("Initialize Database") try: projectPath = self.__projectPath() except PyramidNoProjectSelectedException: E5MessageBox.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() ################################################################## ## 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 PyramidNoProjectSelectedException: E5MessageBox.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.Normal, "/") if not ok or url == "": return cmd = self.getPyramidCommand("pviews") 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 PyramidNoProjectSelectedException: E5MessageBox.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 PyramidNoProjectSelectedException: E5MessageBox.warning( self.__ui, title, self.tr('No current Pyramid project selected or no Pyramid' ' project created yet. Aborting...')) return cmd = self.getPyramidCommand("ptweens") 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 (string) @return extracted locale (string) or None """ if self.__e5project.getTranslationPattern(): # On Windows, path typically contains backslashes. This leads # to an invalid seach pattern '...\(' because the opening bracked # will be escaped. pattern = self.__e5project.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 (list of string) @return normalized file names (list of string) """ 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 (list of string) @return file names belonging to the current site (list of string) """ 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 PyramidNoProjectSelectedException: E5MessageBox.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: E5MessageBox.warning( self.__ui, title, self.tr('No setup.cfg found or no "extract_messages"' ' section found in setup.cfg.')) return except configparser.NoOptionError: E5MessageBox.warning( self.__ui, title, self.tr('No "output_file" option found in setup.cfg.')) return try: path = os.path.join(projectPath, os.path.dirname(potFile)) os.makedirs(path) except OSError: pass 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.__e5project.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 (string) """ title = self.tr( "Initializing message catalog for '{0}'").format(code) try: projectPath = self.__projectPath() except PyramidNoProjectSelectedException: E5MessageBox.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.__e5project.getTranslationPattern().replace( "%language%", code) self.__e5project.appendFile(langFile) def compileCatalogs(self, filenames): """ Public method to compile the message catalogs. @param filenames list of filenames (not used) """ title = self.tr("Compiling message catalogs") try: projectPath = self.__projectPath() except PyramidNoProjectSelectedException: E5MessageBox.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.__e5project.appendFile(fullName) def compileSelectedCatalogs(self, filenames): """ Public method to update the message catalogs. @param filenames list of file names (list of string) """ title = self.tr("Compiling message catalogs") try: projectPath = self.__projectPath() except PyramidNoProjectSelectedException: E5MessageBox.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: E5MessageBox.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.__e5project.appendFile(fullName) def updateCatalogs(self, filenames): """ Public method to update the message catalogs. @param filenames list of filenames (not used) """ title = self.tr("Updating message catalogs") try: projectPath = self.__projectPath() except PyramidNoProjectSelectedException: E5MessageBox.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 """ title = self.tr("Updating message catalogs") try: projectPath = self.__projectPath() except PyramidNoProjectSelectedException: E5MessageBox.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: E5MessageBox.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 (string) """ editor = self.__plugin.getPreferences("TranslationsEditor") if poFile.endswith(".po") and editor: try: wd = self.__projectPath() except PyramidNoProjectSelectedException: wd = "" started, pid = QProcess.startDetached(editor, [poFile], wd) if not started: E5MessageBox.critical( None, self.tr('Process Generation Error'), self.tr('The translations editor process ({0}) could' ' not be started.').format( os.path.basename(editor)))