ProjectFlask/Project.py

changeset 17
f31df56510a1
parent 16
dd3f6bfb85f7
child 18
d76a0939be6a
diff -r dd3f6bfb85f7 -r f31df56510a1 ProjectFlask/Project.py
--- a/ProjectFlask/Project.py	Sat Nov 21 17:50:57 2020 +0100
+++ b/ProjectFlask/Project.py	Sat Nov 21 20:37:54 2020 +0100
@@ -8,12 +8,11 @@
 """
 
 import os
-import re
 
 from PyQt5.QtCore import (
     pyqtSlot, QObject, QProcess, QProcessEnvironment, QTimer
 )
-from PyQt5.QtWidgets import QMenu, QDialog
+from PyQt5.QtWidgets import QMenu
 
 from E5Gui import E5MessageBox
 from E5Gui.E5Action import E5Action
@@ -25,10 +24,10 @@
 import Utilities
 
 from .FlaskCommandDialog import FlaskCommandDialog
-from .PyBabelCommandDialog import PyBabelCommandDialog
+
+from .FlaskBabelExtension.PyBabelProjectExtension import PyBabelProject
 
 
-# TODO: move PyBabel related code to a separate package (FlaskBabelExtension)
 # TODO: move database related code to a separate package (FlaskMigrateExtension)
 class Project(QObject):
     """
@@ -72,10 +71,9 @@
             "werkzeug": "",
         }
         
-        self.__capabilities = {
-            "pybabel": False,
-            "migrate": False,
-        }
+        self.__capabilities = {}
+        
+        self.__pybabelProject = PyBabelProject(self.__plugin, self, self.__ui)
     
     def initActions(self):
         """
@@ -201,7 +199,8 @@
             """<b>Configure PyBabel</b>"""
             """<p>Shows a dialog to edit the configuration for pybabel.</p>"""
         ))
-        self.pybabelConfigAct.triggered.connect(self.__configurePybabel)
+        self.pybabelConfigAct.triggered.connect(
+            self.__pybabelProject.configurePyBabel)
         self.actions.append(self.pybabelConfigAct)
         
         ##################################
@@ -296,21 +295,6 @@
         """
         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.
@@ -325,66 +309,19 @@
 ##            
             self.__determineCapabilities()
             
-            if self.__capabilities["pybabel"]:
-                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(
-                    "generateAllWithObsolete", self.updateCatalogsObsolete,
-                    self.tr("Update All Catalogs (with obsolete)"))
-                self.__translationsBrowser.addHookMethodAndMenuEntry(
-                    "generateSelected", self.updateSelectedCatalogs,
-                    self.tr("Update Selected Catalogs"))
-                self.__translationsBrowser.addHookMethodAndMenuEntry(
-                    "generateSelectedWithObsolete",
-                    self.updateSelectedCatalogsObsolete,
-                    self.tr("Update Selected Catalogs (with obsolete)"))
-                
-                self.__hooksInstalled = True
-            
-                self.registerOpenHook()
+            self.__pybabelProject.projectOpenedHooks()
+##            self.__hooksInstalled = True
     
     def projectClosedHooks(self):
         """
         Public method to remove our hook methods.
         """
+        self.__pybabelProject.projectClosedHooks()
+        
         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(
-                "generateAllWithObsolete")
-            self.__translationsBrowser.removeHookMethod(
-                "generateSelected")
-            self.__translationsBrowser.removeHookMethod(
-                "generateSelectedWithObsolete")
-            self.__translationsBrowser.removeHookMethod(
-                "open")
-            self.__translationsBrowser = None
+            pass
         
         self.__hooksInstalled = False
     
@@ -483,20 +420,11 @@
         @return full flask command
         @rtype str
         """
-        return self.__getFullCommand("flask")
+        return self.getFullCommand("flask")
     
-    def getBabelCommand(self):
+    def getFullCommand(self, command):
         """
-        Public method to build the Babel command.
-        
-        @return full pybabel command
-        @rtype str
-        """
-        return self.__getFullCommand("pybabel")
-    
-    def __getFullCommand(self, command):
-        """
-        Private method to get the full command for a given command name.
+        Public method to get the full command for a given command name.
         
         @param command command name
         @type str
@@ -683,12 +611,37 @@
         extensions.
         """
         # 1. support for flask-babel (i.e. pybabel)
-        self.__capabilities["pybabel"] = self.flaskBabelAvailable()
-        self.pybabelConfigAct.setEnabled(self.__capabilities["pybabel"])
+        self.__pybabelProject.determineCapability()
+        self.pybabelConfigAct.setEnabled(self.hasCapability("pybabel"))
         
         # 2. support for flask-migrate
         # TODO: add support for flask-migrate
     
+    def hasCapability(self, key):
+        """
+        Public method to check, if a capability is available.
+        
+        @param key key of the capability to check
+        @type str
+        @return flag indicating the availability of the capability
+        @rtype bool
+        """
+        try:
+            return self.__capabilities[key]
+        except KeyError:
+            return False
+    
+    def setCapability(self, key, available):
+        """
+        Public method to set the availability status of a capability.
+        
+        @param key key of the capability to set
+        @type str
+        @param available flag indicating the availability of the capability
+        @type bool
+        """
+        self.__capabilities[key] = available
+    
     ##################################################################
     ## slot below implements project specific flask configuration
     ##################################################################
@@ -821,431 +774,3 @@
         dlg = FlaskCommandDialog(self)
         if dlg.startCommand("init-db"):
             dlg.exec()
-    
-    ##################################################################
-    ## slots and methods below implement i18n and l10n support
-    ##################################################################
-    
-    def flaskBabelAvailable(self):
-        """
-        Public method to check, if the 'flask-babel' package is available.
-        
-        @return flag indicating the availability of 'flask-babel'
-        @rtype bool
-        """
-        venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
-        interpreter = self.__virtualEnvManager.getVirtualenvInterpreter(
-            venvName)
-        if interpreter and Utilities.isinpath(interpreter):
-            detector = os.path.join(
-                os.path.dirname(__file__), "FlaskBabelDetector.py")
-            proc = QProcess()
-            proc.setProcessChannelMode(QProcess.MergedChannels)
-            proc.start(interpreter, [detector])
-            finished = proc.waitForFinished(30000)
-            if finished and proc.exitCode() == 0:
-                return True
-        
-        return False
-    
-    @pyqtSlot()
-    def __configurePybabel(self):
-        """
-        Private slot to show a dialog to edit the pybabel configuration.
-        """
-        from .PyBabelConfigDialog import PyBabelConfigDialog
-        
-        config = self.getData("pybabel", "")
-        dlg = PyBabelConfigDialog(config)
-        if dlg.exec() == QDialog.Accepted:
-            config = dlg.getConfiguration()
-            self.setData("pybabel", "", config)
-            
-            self.__e5project.setTranslationPattern(os.path.join(
-                config["translationsDirectory"], "%language%", "LC_MESSAGES",
-                "{0}.po".format(config["domain"])
-            ))
-            self.__e5project.setDirty(True)
-            
-            cfgFileName = self.__e5project.getAbsoluteUniversalPath(
-                config["configFile"])
-            if not os.path.exists(cfgFileName):
-                self.__createBabelCfg(cfgFileName)
-    
-    def __ensurePybabelConfigured(self):
-        """
-        Private method to ensure, that PyBabel has been configured.
-        
-        @return flag indicating successful configuration
-        @rtype bool
-        """
-        config = self.getData("pybabel", "")
-        if not config:
-            self.__configurePybabel()
-            return True
-        
-        configFileName = self.getData("pybabel", "configFile")
-        if configFileName:
-            cfgFileName = self.__e5project.getAbsoluteUniversalPath(
-                configFileName)
-            if os.path.exists(cfgFileName):
-                return True
-            else:
-                return self.__createBabelCfg(cfgFileName)
-        
-        return False
-    
-    def __createBabelCfg(self, configFile):
-        """
-        Private method to create a template pybabel configuration file.
-        
-        @param configFile name of the configuration file to be created
-        @type str
-        @return flag indicating successful configuration file creation
-        @rtype bool
-        """
-        _, app = self.getApplication()
-        if app.endswith(".py"):
-            template = (
-                "[python: {0}]\n"
-                "[jinja2: templates/**.html]\n"
-                "extensions=jinja2.ext.autoescape,jinja2.ext.with_\n"
-            )
-        else:
-            template = (
-                "[python: {0}/**.py]\n"
-                "[jinja2: {0}/templates/**.html]\n"
-                "extensions=jinja2.ext.autoescape,jinja2.ext.with_\n"
-            )
-        try:
-            with open(configFile, "w") as f:
-                f.write(template.format(app))
-            self.__e5project.appendFile(configFile)
-            E5MessageBox.information(
-                None,
-                self.tr("Generate PyBabel Configuration File"),
-                self.tr("""The PyBabel configuration file was created."""
-                        """ Please edit it to adjust the entries as"""
-                        """ required.""")
-            )
-            return True
-        except EnvironmentError as err:
-            E5MessageBox.warning(
-                None,
-                self.tr("Generate PyBabel Configuration File"),
-                self.tr("""<p>The PyBabel Configuration File could not be"""
-                        """ generated.</p><p>Reason: {0}</p>""")
-                .format(str(err))
-            )
-            return False
-    
-    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
-        @rtype str or None
-        """
-        if self.__e5project.getTranslationPattern():
-            filename = os.path.splitext(filename)[0] + ".po"
-            
-            # On Windows, path typically contains backslashes. This leads
-            # to an invalid search pattern '...\(' because the opening bracket
-            # 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 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:
-            wd, _ = self.getApplication()
-            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)))
-    
-    def extractMessages(self):
-        """
-        Public method to extract the messages catalog template file.
-        """
-        title = self.tr("Extract messages")
-        if self.__ensurePybabelConfigured():
-            workdir = self.getApplication()[0]
-            potFile = self.__e5project.getAbsoluteUniversalPath(
-                self.getData("pybabel", "catalogFile"))
-            
-            try:
-                potFilePath = os.path.dirname(potFile)
-                os.makedirs(potFilePath)
-            except EnvironmentError:
-                pass
-            
-            args = [
-                "-F",
-                os.path.relpath(
-                    self.__e5project.getAbsoluteUniversalPath(
-                        self.getData("pybabel", "configFile")),
-                    workdir
-                )
-            ]
-            if self.getData("pybabel", "markersList"):
-                for marker in self.getData("pybabel", "markersList"):
-                    args += ["-k", marker]
-            args += [
-                "-o",
-                os.path.relpath(potFile, workdir),
-                "."
-            ]
-            
-            dlg = PyBabelCommandDialog(
-                self, title,
-                msgSuccess=self.tr("\nMessages extracted successfully.")
-            )
-            res = dlg.startCommand("extract", args, workdir)
-            if res:
-                dlg.exec()
-                self.__e5project.appendFile(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)
-        
-        if self.__ensurePybabelConfigured():
-            workdir = self.getApplication()[0]
-            langFile = self.__e5project.getAbsoluteUniversalPath(
-                self.__e5project.getTranslationPattern().replace(
-                    "%language%", code))
-            potFile = self.__e5project.getAbsoluteUniversalPath(
-                self.getData("pybabel", "catalogFile"))
-            
-            args = [
-                "--domain={0}".format(self.getData("pybabel", "domain")),
-                "--input-file={0}".format(os.path.relpath(potFile, workdir)),
-                "--output-file={0}".format(os.path.relpath(langFile, workdir)),
-                "--locale={0}".format(code),
-            ]
-            
-            dlg = PyBabelCommandDialog(
-                self, title,
-                msgSuccess=self.tr(
-                    "\nMessage catalog initialized successfully.")
-            )
-            res = dlg.startCommand("init", args, workdir)
-            if res:
-                dlg.exec()
-                
-                self.__e5project.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")
-        
-        if self.__ensurePybabelConfigured():
-            workdir = self.getApplication()[0]
-            translationsDirectory = self.__e5project.getAbsoluteUniversalPath(
-                self.getData("pybabel", "translationsDirectory"))
-            
-            args = [
-                "--domain={0}".format(self.getData("pybabel", "domain")),
-                "--directory={0}".format(
-                    os.path.relpath(translationsDirectory, workdir)),
-                "--use-fuzzy",
-                "--statistics",
-            ]
-            
-            dlg = PyBabelCommandDialog(
-                self, title,
-                msgSuccess=self.tr("\nMessage catalogs compiled successfully.")
-            )
-            res = dlg.startCommand("compile", args, workdir)
-            if res:
-                dlg.exec()
-            
-                for entry in os.walk(translationsDirectory):
-                    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
-        @type list of str
-        """
-        title = self.tr("Compiling message catalogs")
-        
-        locales = {self.__getLocale(f) for f in filenames}
-        
-        if len(locales) == 0:
-            E5MessageBox.warning(
-                self.__ui,
-                title,
-                self.tr('No locales detected. Aborting...'))
-            return
-        
-        if self.__ensurePybabelConfigured():
-            workdir = self.getApplication()[0]
-            translationsDirectory = self.__e5project.getAbsoluteUniversalPath(
-                self.getData("pybabel", "translationsDirectory"))
-            
-            argsList = []
-            for loc in locales:
-                argsList.append([
-                    "compile",
-                    "--domain={0}".format(self.getData("pybabel", "domain")),
-                    "--directory={0}".format(
-                        os.path.relpath(translationsDirectory, workdir)),
-                    "--use-fuzzy",
-                    "--statistics",
-                    "--locale={0}".format(loc),
-                ])
-            
-            dlg = PyBabelCommandDialog(
-                self, title=title,
-                msgSuccess=self.tr("\nMessage catalogs compiled successfully.")
-            )
-            res = dlg.startBatchCommand(argsList, workdir)
-            if res:
-                dlg.exec()
-            
-                for entry in os.walk(translationsDirectory):
-                    for fileName in entry[2]:
-                        fullName = os.path.join(entry[0], fileName)
-                        if fullName.endswith('.mo'):
-                            self.__e5project.appendFile(fullName)
-    
-    def updateCatalogs(self, filenames, withObsolete=False):
-        """
-        Public method to update the message catalogs.
-        
-        @param filenames list of filenames (not used)
-        @type list of str
-        @param withObsolete flag indicating to keep obsolete translations
-        @type bool
-        """
-        title = self.tr("Updating message catalogs")
-        
-        if self.__ensurePybabelConfigured():
-            workdir = self.getApplication()[0]
-            translationsDirectory = self.__e5project.getAbsoluteUniversalPath(
-                self.getData("pybabel", "translationsDirectory"))
-            potFile = self.__e5project.getAbsoluteUniversalPath(
-                self.getData("pybabel", "catalogFile"))
-            
-            args = [
-                "--domain={0}".format(self.getData("pybabel", "domain")),
-                "--input-file={0}".format(os.path.relpath(potFile, workdir)),
-                "--output-dir={0}".format(
-                    os.path.relpath(translationsDirectory, workdir)),
-            ]
-            if not withObsolete:
-                args.append("--ignore-obsolete")
-            
-            dlg = PyBabelCommandDialog(
-                self, title,
-                msgSuccess=self.tr("\nMessage catalogs updated successfully.")
-            )
-            res = dlg.startCommand("update", args, workdir)
-            if res:
-                dlg.exec()
-    
-    def updateCatalogsObsolete(self, filenames):
-        """
-        Public method to update the message catalogs keeping obsolete
-        translations.
-        
-        @param filenames list of filenames (not used)
-        @type list of str
-        """
-        self.updateCatalogs(filenames, withObsolete=True)
-    
-    def updateSelectedCatalogs(self, filenames, withObsolete=False):
-        """
-        Public method to update the selected message catalogs.
-        
-        @param filenames list of filenames
-        @type list of str
-        @param withObsolete flag indicating to keep obsolete translations
-        @type bool
-        """
-        title = self.tr("Updating message catalogs")
-        
-        locales = {self.__getLocale(f) for f in filenames}
-        
-        if len(locales) == 0:
-            E5MessageBox.warning(
-                self.__ui,
-                title,
-                self.tr('No locales detected. Aborting...'))
-            return
-        
-        if self.__ensurePybabelConfigured():
-            workdir = self.getApplication()[0]
-            translationsDirectory = self.__e5project.getAbsoluteUniversalPath(
-                self.getData("pybabel", "translationsDirectory"))
-            potFile = self.__e5project.getAbsoluteUniversalPath(
-                self.getData("pybabel", "catalogFile"))
-            argsList = []
-            for loc in locales:
-                args = [
-                    "update",
-                    "--domain={0}".format(self.getData("pybabel", "domain")),
-                    "--input-file={0}".format(
-                        os.path.relpath(potFile, workdir)),
-                    "--output-dir={0}".format(
-                        os.path.relpath(translationsDirectory, workdir)),
-                    "--locale={0}".format(loc),
-                ]
-                if not withObsolete:
-                    args.append("--ignore-obsolete")
-                argsList.append(args)
-            
-            dlg = PyBabelCommandDialog(
-                self, title=title,
-                msgSuccess=self.tr("\nMessage catalogs updated successfully.")
-            )
-            res = dlg.startBatchCommand(argsList, workdir)
-            if res:
-                dlg.exec()
-    
-    def updateSelectedCatalogsObsolete(self, filenames):
-        """
-        Public method to update the message catalogs keeping obsolete
-        translations.
-        
-        @param filenames list of filenames (not used)
-        @type list of str
-        """
-        self.updateSelectedCatalogs(filenames, withObsolete=True)

eric ide

mercurial