ProjectFlask/FlaskBabelExtension/PyBabelProjectExtension.py

branch
eric7
changeset 64
0ee58185b8df
parent 61
fe1e8783a95f
child 66
0d3168d0e310
equal deleted inserted replaced
63:7c05cbc8b3e5 64:0ee58185b8df
9 9
10 import os 10 import os
11 import re 11 import re
12 import contextlib 12 import contextlib
13 13
14 from PyQt5.QtCore import pyqtSlot, QObject, QProcess 14 from PyQt6.QtCore import pyqtSlot, QObject, QProcess
15 from PyQt5.QtWidgets import QDialog, QMenu 15 from PyQt6.QtWidgets import QDialog, QMenu
16 16
17 from E5Gui import E5MessageBox 17 from EricWidgets import EricMessageBox
18 from E5Gui.E5Application import e5App 18 from EricWidgets.EricApplication import ericApp
19 from E5Gui.E5Action import E5Action 19 from EricGui.EricAction import EricAction
20 20
21 from .PyBabelCommandDialog import PyBabelCommandDialog 21 from .PyBabelCommandDialog import PyBabelCommandDialog
22 22
23 import Utilities 23 import Utilities
24 24
41 super().__init__(parent) 41 super().__init__(parent)
42 42
43 self.__plugin = plugin 43 self.__plugin = plugin
44 self.__project = project 44 self.__project = project
45 45
46 self.__e5project = e5App().getObject("Project") 46 self.__ericProject = ericApp().getObject("Project")
47 47
48 self.__hooksInstalled = False 48 self.__hooksInstalled = False
49 49
50 def initActions(self): 50 def initActions(self):
51 """ 51 """
52 Public method to define the flask-babel actions. 52 Public method to define the flask-babel actions.
53 """ 53 """
54 self.actions = [] 54 self.actions = []
55 55
56 self.pybabelConfigAct = E5Action( 56 self.pybabelConfigAct = EricAction(
57 self.tr('Configure flask-babel'), 57 self.tr('Configure flask-babel'),
58 self.tr('&Configure flask-babel'), 58 self.tr('&Configure flask-babel'),
59 0, 0, 59 0, 0,
60 self, 'flask_config_pybabel') 60 self, 'flask_config_pybabel')
61 self.pybabelConfigAct.setStatusTip(self.tr( 61 self.pybabelConfigAct.setStatusTip(self.tr(
67 )) 67 ))
68 self.pybabelConfigAct.triggered.connect( 68 self.pybabelConfigAct.triggered.connect(
69 self.__configurePyBabel) 69 self.__configurePyBabel)
70 self.actions.append(self.pybabelConfigAct) 70 self.actions.append(self.pybabelConfigAct)
71 71
72 self.pybabelInstallAct = E5Action( 72 self.pybabelInstallAct = EricAction(
73 self.tr('Install flask-babel'), 73 self.tr('Install flask-babel'),
74 self.tr('&Install flask-babel'), 74 self.tr('&Install flask-babel'),
75 0, 0, 75 0, 0,
76 self, 'flask_install_pybabel') 76 self, 'flask_install_pybabel')
77 self.pybabelInstallAct.setStatusTip(self.tr( 77 self.pybabelInstallAct.setStatusTip(self.tr(
84 )) 84 ))
85 self.pybabelInstallAct.triggered.connect( 85 self.pybabelInstallAct.triggered.connect(
86 self.__installFlaskBabel) 86 self.__installFlaskBabel)
87 self.actions.append(self.pybabelInstallAct) 87 self.actions.append(self.pybabelInstallAct)
88 88
89 self.pybabelAvailabilityAct = E5Action( 89 self.pybabelAvailabilityAct = EricAction(
90 self.tr('Check flask-babel Availability'), 90 self.tr('Check flask-babel Availability'),
91 self.tr('Check flask-babel &Availability'), 91 self.tr('Check flask-babel &Availability'),
92 0, 0, 92 0, 0,
93 self, 'flask_check_pybabel') 93 self, 'flask_check_pybabel')
94 self.pybabelAvailabilityAct.setStatusTip(self.tr( 94 self.pybabelAvailabilityAct.setStatusTip(self.tr(
136 def projectOpenedHooks(self): 136 def projectOpenedHooks(self):
137 """ 137 """
138 Public method to add our hook methods. 138 Public method to add our hook methods.
139 """ 139 """
140 if self.__project.hasCapability("flask-babel"): 140 if self.__project.hasCapability("flask-babel"):
141 self.__e5project.projectLanguageAddedByCode.connect( 141 self.__ericProject.projectLanguageAddedByCode.connect(
142 self.__projectLanguageAdded) 142 self.__projectLanguageAdded)
143 self.__translationsBrowser = ( 143 self.__translationsBrowser = (
144 e5App().getObject("ProjectBrowser") 144 ericApp().getObject("ProjectBrowser")
145 .getProjectBrowser("translations")) 145 .getProjectBrowser("translations"))
146 self.__translationsBrowser.addHookMethodAndMenuEntry( 146 self.__translationsBrowser.addHookMethodAndMenuEntry(
147 "extractMessages", self.extractMessages, 147 "extractMessages", self.extractMessages,
148 self.tr("Extract Messages")) 148 self.tr("Extract Messages"))
149 self.__translationsBrowser.addHookMethodAndMenuEntry( 149 self.__translationsBrowser.addHookMethodAndMenuEntry(
173 def projectClosedHooks(self): 173 def projectClosedHooks(self):
174 """ 174 """
175 Public method to remove our hook methods. 175 Public method to remove our hook methods.
176 """ 176 """
177 if self.__hooksInstalled: 177 if self.__hooksInstalled:
178 self.__e5project.projectLanguageAddedByCode.disconnect( 178 self.__ericProject.projectLanguageAddedByCode.disconnect(
179 self.__projectLanguageAdded) 179 self.__projectLanguageAdded)
180 self.__translationsBrowser.removeHookMethod( 180 self.__translationsBrowser.removeHookMethod(
181 "extractMessages") 181 "extractMessages")
182 self.__translationsBrowser.removeHookMethod( 182 self.__translationsBrowser.removeHookMethod(
183 "releaseAll") 183 "releaseAll")
238 interpreter = self.__project.getVirtualenvInterpreter() 238 interpreter = self.__project.getVirtualenvInterpreter()
239 if interpreter and Utilities.isinpath(interpreter): 239 if interpreter and Utilities.isinpath(interpreter):
240 detector = os.path.join( 240 detector = os.path.join(
241 os.path.dirname(__file__), "FlaskBabelDetector.py") 241 os.path.dirname(__file__), "FlaskBabelDetector.py")
242 proc = QProcess() 242 proc = QProcess()
243 proc.setProcessChannelMode(QProcess.MergedChannels) 243 proc.setProcessChannelMode(
244 QProcess.ProcessChannelMode.MergedChannels)
244 proc.start(interpreter, [detector]) 245 proc.start(interpreter, [detector])
245 finished = proc.waitForFinished(30000) 246 finished = proc.waitForFinished(30000)
246 if finished and proc.exitCode() == 0: 247 if finished and proc.exitCode() == 0:
247 return True 248 return True
248 249
255 """ 256 """
256 from .PyBabelConfigDialog import PyBabelConfigDialog 257 from .PyBabelConfigDialog import PyBabelConfigDialog
257 258
258 config = self.__project.getData("flask-babel", "") 259 config = self.__project.getData("flask-babel", "")
259 dlg = PyBabelConfigDialog(config) 260 dlg = PyBabelConfigDialog(config)
260 if dlg.exec() == QDialog.Accepted: 261 if dlg.exec() == QDialog.DialogCode.Accepted:
261 config = dlg.getConfiguration() 262 config = dlg.getConfiguration()
262 self.__project.setData("flask-babel", "", config) 263 self.__project.setData("flask-babel", "", config)
263 264
264 self.__e5project.setTranslationPattern(os.path.join( 265 self.__ericProject.setTranslationPattern(os.path.join(
265 config["translationsDirectory"], "%language%", "LC_MESSAGES", 266 config["translationsDirectory"], "%language%", "LC_MESSAGES",
266 "{0}.po".format(config["domain"]) 267 "{0}.po".format(config["domain"])
267 )) 268 ))
268 self.__e5project.setDirty(True) 269 self.__ericProject.setDirty(True)
269 270
270 cfgFileName = self.__e5project.getAbsoluteUniversalPath( 271 cfgFileName = self.__ericProject.getAbsoluteUniversalPath(
271 config["configFile"]) 272 config["configFile"])
272 if not os.path.exists(cfgFileName): 273 if not os.path.exists(cfgFileName):
273 self.__createBabelCfg(cfgFileName) 274 self.__createBabelCfg(cfgFileName)
274 275
275 def __ensurePybabelConfigured(self): 276 def __ensurePybabelConfigured(self):
284 self.__configurePybabel() 285 self.__configurePybabel()
285 return True 286 return True
286 287
287 configFileName = self.__project.getData("flask-babel", "configFile") 288 configFileName = self.__project.getData("flask-babel", "configFile")
288 if configFileName: 289 if configFileName:
289 cfgFileName = self.__e5project.getAbsoluteUniversalPath( 290 cfgFileName = self.__ericProject.getAbsoluteUniversalPath(
290 configFileName) 291 configFileName)
291 if os.path.exists(cfgFileName): 292 if os.path.exists(cfgFileName):
292 return True 293 return True
293 else: 294 else:
294 return self.__createBabelCfg(cfgFileName) 295 return self.__createBabelCfg(cfgFileName)
315 "extensions=jinja2.ext.autoescape,jinja2.ext.with_\n") 316 "extensions=jinja2.ext.autoescape,jinja2.ext.with_\n")
316 ) 317 )
317 try: 318 try:
318 with open(configFile, "w") as f: 319 with open(configFile, "w") as f:
319 f.write(template.format(app)) 320 f.write(template.format(app))
320 self.__e5project.appendFile(configFile) 321 self.__ericProject.appendFile(configFile)
321 E5MessageBox.information( 322 EricMessageBox.information(
322 None, 323 None,
323 self.tr("Generate PyBabel Configuration File"), 324 self.tr("Generate PyBabel Configuration File"),
324 self.tr("""The PyBabel configuration file was created.""" 325 self.tr("""The PyBabel configuration file was created."""
325 """ Please edit it to adjust the entries as""" 326 """ Please edit it to adjust the entries as"""
326 """ required.""") 327 """ required.""")
327 ) 328 )
328 return True 329 return True
329 except OSError as err: 330 except OSError as err:
330 E5MessageBox.warning( 331 EricMessageBox.warning(
331 None, 332 None,
332 self.tr("Generate PyBabel Configuration File"), 333 self.tr("Generate PyBabel Configuration File"),
333 self.tr("""<p>The PyBabel Configuration File could not be""" 334 self.tr("""<p>The PyBabel Configuration File could not be"""
334 """ generated.</p><p>Reason: {0}</p>""") 335 """ generated.</p><p>Reason: {0}</p>""")
335 .format(str(err)) 336 .format(str(err))
343 environment. 344 environment.
344 """ 345 """
345 venvName = self.__project.getVirtualEnvironment() 346 venvName = self.__project.getVirtualEnvironment()
346 if venvName: 347 if venvName:
347 interpreter = self.__project.getFullCommand("python") 348 interpreter = self.__project.getFullCommand("python")
348 pip = e5App().getObject("Pip") 349 pip = ericApp().getObject("Pip")
349 pip.installPackages(["flask-babel"], interpreter=interpreter) 350 pip.installPackages(["flask-babel"], interpreter=interpreter)
350 self.determineCapability() 351 self.determineCapability()
351 else: 352 else:
352 E5MessageBox.critical( 353 EricMessageBox.critical(
353 None, 354 None,
354 self.tr("Install flask-babel"), 355 self.tr("Install flask-babel"),
355 self.tr("The 'flask-babel' extension could not be installed" 356 self.tr("The 'flask-babel' extension could not be installed"
356 " because no virtual environment has been" 357 " because no virtual environment has been"
357 " configured.")) 358 " configured."))
365 msg = ( 366 msg = (
366 self.tr("The 'flask-babel' extension is installed.") 367 self.tr("The 'flask-babel' extension is installed.")
367 if self.__project.hasCapability("flask-babel") else 368 if self.__project.hasCapability("flask-babel") else
368 self.tr("The 'flask-babel' extension is not installed.") 369 self.tr("The 'flask-babel' extension is not installed.")
369 ) 370 )
370 E5MessageBox.information( 371 EricMessageBox.information(
371 None, 372 None,
372 self.tr("flask-babel Availability"), 373 self.tr("flask-babel Availability"),
373 msg) 374 msg)
374 375
375 def __getLocale(self, filename): 376 def __getLocale(self, filename):
379 @param filename name of the file used for extraction 380 @param filename name of the file used for extraction
380 @type str 381 @type str
381 @return extracted locale 382 @return extracted locale
382 @rtype str or None 383 @rtype str or None
383 """ 384 """
384 if self.__e5project.getTranslationPattern(): 385 if self.__ericProject.getTranslationPattern():
385 filename = os.path.splitext(filename)[0] + ".po" 386 filename = os.path.splitext(filename)[0] + ".po"
386 387
387 # On Windows, path typically contains backslashes. This leads 388 # On Windows, path typically contains backslashes. This leads
388 # to an invalid search pattern '...\(' because the opening bracket 389 # to an invalid search pattern '...\(' because the opening bracket
389 # will be escaped. 390 # will be escaped.
390 pattern = self.__e5project.getTranslationPattern() 391 pattern = self.__ericProject.getTranslationPattern()
391 pattern = os.path.normpath(pattern) 392 pattern = os.path.normpath(pattern)
392 pattern = pattern.replace("%language%", "(.*?)") 393 pattern = pattern.replace("%language%", "(.*?)")
393 pattern = pattern.replace('\\', '\\\\') 394 pattern = pattern.replace('\\', '\\\\')
394 match = re.search(pattern, filename) 395 match = re.search(pattern, filename)
395 if match is not None: 396 if match is not None:
407 editor = self.__plugin.getPreferences("TranslationsEditor") 408 editor = self.__plugin.getPreferences("TranslationsEditor")
408 if poFile.endswith(".po") and editor: 409 if poFile.endswith(".po") and editor:
409 workdir = self.__project.getApplication()[0] 410 workdir = self.__project.getApplication()[0]
410 started, pid = QProcess.startDetached(editor, [poFile], workdir) 411 started, pid = QProcess.startDetached(editor, [poFile], workdir)
411 if not started: 412 if not started:
412 E5MessageBox.critical( 413 EricMessageBox.critical(
413 None, 414 None,
414 self.tr('Process Generation Error'), 415 self.tr('Process Generation Error'),
415 self.tr('The translations editor process ({0}) could' 416 self.tr('The translations editor process ({0}) could'
416 ' not be started.').format( 417 ' not be started.').format(
417 os.path.basename(editor))) 418 os.path.basename(editor)))
421 Public method to extract the messages catalog template file. 422 Public method to extract the messages catalog template file.
422 """ 423 """
423 title = self.tr("Extract messages") 424 title = self.tr("Extract messages")
424 if self.__ensurePybabelConfigured(): 425 if self.__ensurePybabelConfigured():
425 workdir = self.__project.getApplication()[0] 426 workdir = self.__project.getApplication()[0]
426 potFile = self.__e5project.getAbsoluteUniversalPath( 427 potFile = self.__ericProject.getAbsoluteUniversalPath(
427 self.__project.getData("flask-babel", "catalogFile")) 428 self.__project.getData("flask-babel", "catalogFile"))
428 429
429 with contextlib.suppress(OSError): 430 with contextlib.suppress(OSError):
430 potFilePath = os.path.dirname(potFile) 431 potFilePath = os.path.dirname(potFile)
431 os.makedirs(potFilePath) 432 os.makedirs(potFilePath)
432 433
433 args = [ 434 args = [
434 "-F", 435 "-F",
435 os.path.relpath( 436 os.path.relpath(
436 self.__e5project.getAbsoluteUniversalPath( 437 self.__ericProject.getAbsoluteUniversalPath(
437 self.__project.getData("flask-babel", "configFile")), 438 self.__project.getData("flask-babel", "configFile")),
438 workdir 439 workdir
439 ) 440 )
440 ] 441 ]
441 if self.__project.getData("flask-babel", "markersList"): 442 if self.__project.getData("flask-babel", "markersList"):
453 msgSuccess=self.tr("\nMessages extracted successfully.") 454 msgSuccess=self.tr("\nMessages extracted successfully.")
454 ) 455 )
455 res = dlg.startCommand("extract", args, workdir) 456 res = dlg.startCommand("extract", args, workdir)
456 if res: 457 if res:
457 dlg.exec() 458 dlg.exec()
458 self.__e5project.appendFile(potFile) 459 self.__ericProject.appendFile(potFile)
459 460
460 def __projectLanguageAdded(self, code): 461 def __projectLanguageAdded(self, code):
461 """ 462 """
462 Private slot handling the addition of a new language. 463 Private slot handling the addition of a new language.
463 464
467 title = self.tr( 468 title = self.tr(
468 "Initializing message catalog for '{0}'").format(code) 469 "Initializing message catalog for '{0}'").format(code)
469 470
470 if self.__ensurePybabelConfigured(): 471 if self.__ensurePybabelConfigured():
471 workdir = self.__project.getApplication()[0] 472 workdir = self.__project.getApplication()[0]
472 langFile = self.__e5project.getAbsoluteUniversalPath( 473 langFile = self.__ericProject.getAbsoluteUniversalPath(
473 self.__e5project.getTranslationPattern().replace( 474 self.__ericProject.getTranslationPattern().replace(
474 "%language%", code)) 475 "%language%", code))
475 potFile = self.__e5project.getAbsoluteUniversalPath( 476 potFile = self.__ericProject.getAbsoluteUniversalPath(
476 self.__project.getData("flask-babel", "catalogFile")) 477 self.__project.getData("flask-babel", "catalogFile"))
477 478
478 args = [ 479 args = [
479 "--domain={0}".format( 480 "--domain={0}".format(
480 self.__project.getData("flask-babel", "domain")), 481 self.__project.getData("flask-babel", "domain")),
490 ) 491 )
491 res = dlg.startCommand("init", args, workdir) 492 res = dlg.startCommand("init", args, workdir)
492 if res: 493 if res:
493 dlg.exec() 494 dlg.exec()
494 495
495 self.__e5project.appendFile(langFile) 496 self.__ericProject.appendFile(langFile)
496 497
497 def compileCatalogs(self, filenames): 498 def compileCatalogs(self, filenames):
498 """ 499 """
499 Public method to compile the message catalogs. 500 Public method to compile the message catalogs.
500 501
503 """ 504 """
504 title = self.tr("Compiling message catalogs") 505 title = self.tr("Compiling message catalogs")
505 506
506 if self.__ensurePybabelConfigured(): 507 if self.__ensurePybabelConfigured():
507 workdir = self.__project.getApplication()[0] 508 workdir = self.__project.getApplication()[0]
508 translationsDirectory = self.__e5project.getAbsoluteUniversalPath( 509 translationsDirectory = (
509 self.__project.getData("flask-babel", "translationsDirectory")) 510 self.__ericProject.getAbsoluteUniversalPath(
511 self.__project.getData(
512 "flask-babel", "translationsDirectory")
513 )
514 )
510 515
511 args = [ 516 args = [
512 "--domain={0}".format( 517 "--domain={0}".format(
513 self.__project.getData("flask-babel", "domain")), 518 self.__project.getData("flask-babel", "domain")),
514 "--directory={0}".format( 519 "--directory={0}".format(
527 532
528 for entry in os.walk(translationsDirectory): 533 for entry in os.walk(translationsDirectory):
529 for fileName in entry[2]: 534 for fileName in entry[2]:
530 fullName = os.path.join(entry[0], fileName) 535 fullName = os.path.join(entry[0], fileName)
531 if fullName.endswith('.mo'): 536 if fullName.endswith('.mo'):
532 self.__e5project.appendFile(fullName) 537 self.__ericProject.appendFile(fullName)
533 538
534 def compileSelectedCatalogs(self, filenames): 539 def compileSelectedCatalogs(self, filenames):
535 """ 540 """
536 Public method to update the message catalogs. 541 Public method to update the message catalogs.
537 542
541 title = self.tr("Compiling message catalogs") 546 title = self.tr("Compiling message catalogs")
542 547
543 locales = {self.__getLocale(f) for f in filenames} 548 locales = {self.__getLocale(f) for f in filenames}
544 549
545 if len(locales) == 0: 550 if len(locales) == 0:
546 E5MessageBox.warning( 551 EricMessageBox.warning(
547 self.__ui, 552 self.__ui,
548 title, 553 title,
549 self.tr('No locales detected. Aborting...')) 554 self.tr('No locales detected. Aborting...'))
550 return 555 return
551 556
552 if self.__ensurePybabelConfigured(): 557 if self.__ensurePybabelConfigured():
553 workdir = self.__project.getApplication()[0] 558 workdir = self.__project.getApplication()[0]
554 translationsDirectory = self.__e5project.getAbsoluteUniversalPath( 559 translationsDirectory = (
555 self.__project.getData("flask-babel", "translationsDirectory")) 560 self.__ericProject.getAbsoluteUniversalPath(
561 self.__project.getData(
562 "flask-babel", "translationsDirectory")
563 )
564 )
556 565
557 argsList = [] 566 argsList = []
558 for loc in locales: 567 for loc in locales:
559 argsList.append([ 568 argsList.append([
560 "compile", 569 "compile",
577 586
578 for entry in os.walk(translationsDirectory): 587 for entry in os.walk(translationsDirectory):
579 for fileName in entry[2]: 588 for fileName in entry[2]:
580 fullName = os.path.join(entry[0], fileName) 589 fullName = os.path.join(entry[0], fileName)
581 if fullName.endswith('.mo'): 590 if fullName.endswith('.mo'):
582 self.__e5project.appendFile(fullName) 591 self.__ericProject.appendFile(fullName)
583 592
584 def updateCatalogs(self, filenames, withObsolete=False): 593 def updateCatalogs(self, filenames, withObsolete=False):
585 """ 594 """
586 Public method to update the message catalogs. 595 Public method to update the message catalogs.
587 596
592 """ 601 """
593 title = self.tr("Updating message catalogs") 602 title = self.tr("Updating message catalogs")
594 603
595 if self.__ensurePybabelConfigured(): 604 if self.__ensurePybabelConfigured():
596 workdir = self.__project.getApplication()[0] 605 workdir = self.__project.getApplication()[0]
597 translationsDirectory = self.__e5project.getAbsoluteUniversalPath( 606 translationsDirectory = (
598 self.__project.getData("flask-babel", "translationsDirectory")) 607 self.__ericProject.getAbsoluteUniversalPath(
599 potFile = self.__e5project.getAbsoluteUniversalPath( 608 self.__project.getData(
609 "flask-babel", "translationsDirectory")
610 )
611 )
612 potFile = self.__ericProject.getAbsoluteUniversalPath(
600 self.__project.getData("flask-babel", "catalogFile")) 613 self.__project.getData("flask-babel", "catalogFile"))
601 614
602 args = [ 615 args = [
603 "--domain={0}".format( 616 "--domain={0}".format(
604 self.__project.getData("flask-babel", "domain")), 617 self.__project.getData("flask-babel", "domain")),
639 title = self.tr("Updating message catalogs") 652 title = self.tr("Updating message catalogs")
640 653
641 locales = {self.__getLocale(f) for f in filenames} 654 locales = {self.__getLocale(f) for f in filenames}
642 655
643 if len(locales) == 0: 656 if len(locales) == 0:
644 E5MessageBox.warning( 657 EricMessageBox.warning(
645 self.__ui, 658 self.__ui,
646 title, 659 title,
647 self.tr('No locales detected. Aborting...')) 660 self.tr('No locales detected. Aborting...'))
648 return 661 return
649 662
650 if self.__ensurePybabelConfigured(): 663 if self.__ensurePybabelConfigured():
651 workdir = self.__project.getApplication()[0] 664 workdir = self.__project.getApplication()[0]
652 translationsDirectory = self.__e5project.getAbsoluteUniversalPath( 665 translationsDirectory = (
653 self.__project.getData("flask-babel", "translationsDirectory")) 666 self.__ericProject.getAbsoluteUniversalPath(
654 potFile = self.__e5project.getAbsoluteUniversalPath( 667 self.__project.getData(
668 "flask-babel", "translationsDirectory")
669 )
670 )
671 potFile = self.__ericProject.getAbsoluteUniversalPath(
655 self.__project.getData("flask-babel", "catalogFile")) 672 self.__project.getData("flask-babel", "catalogFile"))
656 argsList = [] 673 argsList = []
657 for loc in locales: 674 for loc in locales:
658 args = [ 675 args = [
659 "update", 676 "update",

eric ide

mercurial