diff -r 874fdd14d3a2 -r 43248bafe9b2 eric6/Project/Project.py --- a/eric6/Project/Project.py Mon Feb 01 10:38:43 2021 +0100 +++ b/eric6/Project/Project.py Tue Mar 02 17:12:08 2021 +0100 @@ -41,6 +41,14 @@ import Preferences import Utilities +from .ProjectFile import ProjectFile +from .UserProjectFile import UserProjectFile +from .DebuggerPropertiesFile import DebuggerPropertiesFile + +from Sessions.SessionFile import SessionFile + +from Tasks.TasksFile import TasksFile + class Project(QObject): """ @@ -184,6 +192,12 @@ self.__initData() + self.__projectFile = ProjectFile(self) + self.__userProjectFile = UserProjectFile(self) + self.__debuggerPropertiesFile = DebuggerPropertiesFile(self) + self.__sessionFile = SessionFile(False) + self.__tasksFile = TasksFile(False) + self.recent = [] self.__loadRecent() @@ -582,6 +596,7 @@ "README": "OTHERS", "README.*": "OTHERS", "*.e4p": "OTHERS", + "*.epj": "OTHERS", "GNUmakefile": "OTHERS", "makefile": "OTHERS", "Makefile": "OTHERS", @@ -787,34 +802,50 @@ def __readProject(self, fn): """ - Private method to read in a project (.e4p) file. + Private method to read in a project (.epj or .e4p) file. @param fn filename of the project file to be read (string) @return flag indicating success """ - f = QFile(fn) - if f.open(QIODevice.ReadOnly): - from E5XML.ProjectReader import ProjectReader - reader = ProjectReader(f, self) - reader.readXML() - res = not reader.hasError() - f.close() + if os.path.splitext(fn)[1] == ".epj": + # new JSON based format + with E5OverrideCursor(): + res = self.__projectFile.readFile(fn) else: - E5MessageBox.critical( - self.ui, - self.tr("Read project file"), - self.tr( - "<p>The project file <b>{0}</b> could not be read.</p>") - .format(fn)) - return False - - self.pfile = os.path.abspath(fn) - self.ppath = os.path.abspath(os.path.dirname(fn)) - - # insert filename into list of recently opened projects - self.__syncRecent() + # old XML based format + f = QFile(fn) + if f.open(QIODevice.ReadOnly): + from E5XML.ProjectReader import ProjectReader + reader = ProjectReader(f, self) + reader.readXML() + res = not reader.hasError() + f.close() + + # create hash value, if it doesn't have one + if reader.version.startswith("5.") and not self.pdata["HASH"]: + hashStr = str(QCryptographicHash.hash( + QByteArray(self.ppath.encode("utf-8")), + QCryptographicHash.Sha1).toHex(), + encoding="utf-8") + self.pdata["HASH"] = hashStr + self.setDirty(True) + else: + E5MessageBox.critical( + self.ui, + self.tr("Read Project File"), + self.tr( + "<p>The project file <b>{0}</b> could not be read." + "</p>") + .format(fn)) + res = False if res: + self.pfile = os.path.abspath(fn) + self.ppath = os.path.abspath(os.path.dirname(fn)) + + # insert filename into list of recently opened projects + self.__syncRecent() + if self.pdata["TRANSLATIONPATTERN"]: self.translationsRoot = self.pdata["TRANSLATIONPATTERN"].split( "%language%")[0] @@ -859,15 +890,6 @@ if dn not in self.otherssubdirs: self.otherssubdirs.append(dn) - # create hash value, if it doesn't have one - if reader.version.startswith("5.") and not self.pdata["HASH"]: - hashStr = str(QCryptographicHash.hash( - QByteArray(self.ppath.encode("utf-8")), - QCryptographicHash.Sha1).toHex(), - encoding="utf-8") - self.pdata["HASH"] = hashStr - self.setDirty(True) - return res def __writeProject(self, fn=None): @@ -896,20 +918,26 @@ if fn is None: fn = self.pfile - f = QFile(fn) - if f.open(QIODevice.WriteOnly): - from E5XML.ProjectWriter import ProjectWriter - ProjectWriter(f, os.path.splitext( - os.path.basename(fn))[0]).writeXML() - res = True + if os.path.splitext(fn)[1] == ".epj": + # new JSON based format + with E5OverrideCursor(): + res = self.__projectFile.writeFile(fn) else: - E5MessageBox.critical( - self.ui, - self.tr("Save project file"), - self.tr( - "<p>The project file <b>{0}</b> could not be" - " written.</p>").format(fn)) - res = False + # old XML based format + f = QFile(fn) + if f.open(QIODevice.WriteOnly): + from E5XML.ProjectWriter import ProjectWriter + ProjectWriter(f, os.path.splitext( + os.path.basename(fn))[0]).writeXML() + res = True + else: + E5MessageBox.critical( + self.ui, + self.tr("Save Project File"), + self.tr( + "<p>The project file <b>{0}</b> could not be" + " written.</p>").format(fn)) + res = False if res: self.pfile = os.path.abspath(fn) @@ -924,52 +952,50 @@ def __readUserProperties(self): """ - Private method to read in the user specific project file (.e4q). + Private method to read in the user specific project file (.eqj or + .e4q). + """ + if self.pfile is None: + return + + fn1, ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), + '{0}.eqj'.format(fn1)) + if os.path.exists(fn): + # try the new JSON based format first + self.__userProjectFile.readFile(fn) + else: + # try the old XML based format second + fn = os.path.join(self.getProjectManagementDir(), + '{0}.e4q'.format(fn1)) + if os.path.exists(fn): + f = QFile(fn) + if f.open(QIODevice.ReadOnly): + from E5XML.UserProjectReader import UserProjectReader + reader = UserProjectReader(f, self) + reader.readXML() + f.close() + else: + E5MessageBox.critical( + self.ui, + self.tr("Read User Project Properties"), + self.tr( + "<p>The user specific project properties file" + " <b>{0}</b> could not be read.</p>").format(fn)) + + def __writeUserProperties(self): + """ + Private method to write the user specific project data to a JSON file. """ if self.pfile is None: return fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), '{0}.e4q'.format(fn)) - if os.path.exists(fn): - f = QFile(fn) - if f.open(QIODevice.ReadOnly): - from E5XML.UserProjectReader import UserProjectReader - reader = UserProjectReader(f, self) - reader.readXML() - f.close() - else: - E5MessageBox.critical( - self.ui, - self.tr("Read user project properties"), - self.tr( - "<p>The user specific project properties file" - " <b>{0}</b> could not be read.</p>").format(fn)) - - def __writeUserProperties(self): - """ - Private method to write the project data to an XML file. - """ - if self.pfile is None: - return - - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), '{0}.e4q'.format(fn)) - - f = QFile(fn) - if f.open(QIODevice.WriteOnly): - from E5XML.UserProjectWriter import UserProjectWriter - UserProjectWriter( - f, os.path.splitext(os.path.basename(fn))[0]).writeXML() - f.close() - else: - E5MessageBox.critical( - self.ui, - self.tr("Save user project properties"), - self.tr( - "<p>The user specific project properties file <b>{0}</b>" - " could not be written.</p>").format(fn)) - + fn = os.path.join(self.getProjectManagementDir(), '{0}.eqj'.format(fn)) + + with E5OverrideCursor(): + self.__userProjectFile.writeFile(fn) + def __showContextMenuSession(self): """ Private slot called before the Session menu is shown. @@ -980,9 +1006,9 @@ else: fn, ext = os.path.splitext(os.path.basename(self.pfile)) fn_new = os.path.join(self.getProjectManagementDir(), - '{0}.e5s'.format(fn)) + '{0}.esj'.format(fn)) fn_old = os.path.join(self.getProjectManagementDir(), - '{0}.e4s'.format(fn)) + '{0}.e5s'.format(fn)) enable = os.path.exists(fn_new) or os.path.exists(fn_old) self.sessActGrp.findChild( QAction, "project_load_session").setEnabled(enable) @@ -992,46 +1018,7 @@ @pyqtSlot() def __readSession(self, quiet=False, indicator=""): """ - Private method to read in the project session file (.e5s or .e4s). - - @param quiet flag indicating quiet operations. - If this flag is true, no errors are reported. - @param indicator indicator string (string) - """ - if self.pfile is None: - if not quiet: - E5MessageBox.critical( - self.ui, - self.tr("Read project session"), - self.tr("Please save the project first.")) - return - - fn1, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), - '{0}{1}.e5s'.format(fn1, indicator)) - if not os.path.exists(fn): - fn = os.path.join(self.getProjectManagementDir(), - '{0}{1}.e4s'.format(fn1, indicator)) - - f = QFile(fn) - if f.open(QIODevice.ReadOnly): - from E5XML.SessionReader import SessionReader - reader = SessionReader(f, False) - reader.readXML(quiet=quiet) - f.close() - else: - if not quiet: - E5MessageBox.critical( - self.ui, - self.tr("Read project session"), - self.tr( - "<p>The project session file <b>{0}</b> could not be" - " read.</p>").format(fn)) - - @pyqtSlot() - def __writeSession(self, quiet=False, indicator=""): - """ - Private method to write the session data to an XML file (.e5s). + Private method to read in the project session file (.esj or .e5s). @param quiet flag indicating quiet operations. If this flag is true, no errors are reported. @@ -1041,29 +1028,59 @@ if not quiet: E5MessageBox.critical( self.ui, - self.tr("Save project session"), + self.tr("Read Project Session"), + self.tr("Please save the project first.")) + return + + fn1, ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), + '{0}{1}.esj'.format(fn1, indicator)) + if os.path.exists(fn): + # try the new JSON based format first + self.__sessionFile.readFile(fn) + else: + # try the old XML based format second + fn = os.path.join(self.getProjectManagementDir(), + '{0}{1}.e5s'.format(fn1, indicator)) + if os.path.exists(fn): + f = QFile(fn) + if f.open(QIODevice.ReadOnly): + from E5XML.SessionReader import SessionReader + reader = SessionReader(f, False) + reader.readXML(quiet=quiet) + f.close() + else: + if not quiet: + E5MessageBox.critical( + self.ui, + self.tr("Read project session"), + self.tr( + "<p>The project session file <b>{0}</b> could" + " not be read.</p>").format(fn)) + + @pyqtSlot() + def __writeSession(self, quiet=False, indicator=""): + """ + Private method to write the session data to an XML file (.esj). + + @param quiet flag indicating quiet operations. + If this flag is true, no errors are reported. + @param indicator indicator string (string) + """ + if self.pfile is None: + if not quiet: + E5MessageBox.critical( + self.ui, + self.tr("Save Project Session"), self.tr("Please save the project first.")) return fn, ext = os.path.splitext(os.path.basename(self.pfile)) fn = os.path.join(self.getProjectManagementDir(), - '{0}{1}.e5s'.format(fn, indicator)) - - f = QFile(fn) - if f.open(QIODevice.WriteOnly): - from E5XML.SessionWriter import SessionWriter - SessionWriter( - f, os.path.splitext(os.path.basename(fn))[0]).writeXML() - f.close() - else: - if not quiet: - E5MessageBox.critical( - self.ui, - self.tr("Save project session"), - self.tr( - "<p>The project session file <b>{0}</b> could not be" - " written.</p>").format(fn)) - + '{0}{1}.esj'.format(fn, indicator)) + + self.__sessionFile.writeFile(fn) + def __deleteSession(self): """ Private method to delete the session file. @@ -1071,88 +1088,76 @@ if self.pfile is None: E5MessageBox.critical( self.ui, - self.tr("Delete project session"), + self.tr("Delete Project Session"), self.tr("Please save the project first.")) return fname, ext = os.path.splitext(os.path.basename(self.pfile)) - for fn in [ - os.path.join( - self.getProjectManagementDir(), "{0}.e5s".format(fname)), - os.path.join( - self.getProjectManagementDir(), "{0}.e4s".format(fname))]: + for ext in (".esj", ".e5s", ".e4s"): + fn = os.path.join( + self.getProjectManagementDir(), "{0}{1}".format(fname, ext)), if os.path.exists(fn): try: os.remove(fn) except OSError: E5MessageBox.critical( self.ui, - self.tr("Delete project session"), + self.tr("Delete Project Session"), self.tr( "<p>The project session file <b>{0}</b> could" " not be deleted.</p>").format(fn)) def __readTasks(self): """ - Private method to read in the project tasks file (.e6t). + Private method to read in the project tasks file (.etj or .e6t). """ if self.pfile is None: E5MessageBox.critical( self.ui, - self.tr("Read tasks"), + self.tr("Read Tasks"), self.tr("Please save the project first.")) return - + base, ext = os.path.splitext(os.path.basename(self.pfile)) fn = os.path.join(self.getProjectManagementDir(), - '{0}.e6t'.format(base)) - if not os.path.exists(fn): - # try again with the old extension + '{0}.etj'.format(base)) + if os.path.exists(fn): + # try new style JSON file first + self.__tasksFile.readFile(fn) + else: + # try old style XML file second fn = os.path.join(self.getProjectManagementDir(), - '{0}.e4t'.format(base)) - if not os.path.exists(fn): - return - f = QFile(fn) - if f.open(QIODevice.ReadOnly): - from E5XML.TasksReader import TasksReader - reader = TasksReader(f, True) - reader.readXML() - f.close() - else: - E5MessageBox.critical( - self.ui, - self.tr("Read tasks"), - self.tr( - "<p>The tasks file <b>{0}</b> could not be read.</p>") - .format(fn)) + '{0}.e6t'.format(base)) + if os.path.exists(fn): + f = QFile(fn) + if f.open(QIODevice.ReadOnly): + from E5XML.TasksReader import TasksReader + reader = TasksReader(f, True) + reader.readXML() + f.close() + else: + E5MessageBox.critical( + self.ui, + self.tr("Read Tasks"), + self.tr( + "<p>The tasks file <b>{0}</b> could not be read." + "</p>") + .format(fn)) def writeTasks(self): """ - Public method to write the tasks data to an XML file (.e6t). + Public method to write the tasks data to a JSON file (.etj). """ if self.pfile is None: return fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), '{0}.e6t'.format(fn)) - f = QFile(fn) - ok = f.open(QIODevice.WriteOnly) - if not ok: - E5MessageBox.critical( - self.ui, - self.tr("Save tasks"), - self.tr( - "<p>The tasks file <b>{0}</b> could not be written.</p>") - .format(fn)) - return - - from E5XML.TasksWriter import TasksWriter - TasksWriter( - f, True, os.path.splitext(os.path.basename(fn))[0]).writeXML() - f.close() - + fn = os.path.join(self.getProjectManagementDir(), + '{0}.etj'.format(fn)) + self.__tasksFile.writeFile(fn) + def __showContextMenuDebugger(self): """ Private slot called before the Debugger menu is shown. @@ -1162,8 +1167,13 @@ enable = False else: fn, ext = os.path.splitext(os.path.basename(self.pfile)) + # try new style file first fn = os.path.join(self.getProjectManagementDir(), - '{0}.e4d'.format(fn)) + '{0}.edj'.format(fn)) + if not os.path.exists(fn): + # try old style file second + fn = os.path.join(self.getProjectManagementDir(), + '{0}.e4d'.format(fn)) enable = os.path.exists(fn) self.dbgActGrp.findChild( QAction, "project_debugger_properties_load").setEnabled(enable) @@ -1173,7 +1183,54 @@ @pyqtSlot() def __readDebugProperties(self, quiet=False): """ - Private method to read in the project debugger properties file (.e4d). + Private method to read in the project debugger properties file + (.edj or .e4d). + + @param quiet flag indicating quiet operations. + If this flag is true, no errors are reported. + """ + if self.pfile is None: + if not quiet: + E5MessageBox.critical( + self.ui, + self.tr("Read Debugger Properties"), + self.tr("Please save the project first.")) + return + + fn1, ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), + '{0}.edj'.format(fn1)) + if os.path.exists(fn): + # try the new JSON based format first + self.__debuggerPropertiesFile.readFile(fn) + else: + # try the old XML based format second + fn = os.path.join(self.getProjectManagementDir(), + '{0}.e4d'.format(fn1)) + + f = QFile(fn) + if f.open(QIODevice.ReadOnly): + from E5XML.DebuggerPropertiesReader import ( + DebuggerPropertiesReader + ) + reader = DebuggerPropertiesReader(f, self) + reader.readXML(quiet=quiet) + f.close() + self.debugPropertiesLoaded = True + self.debugPropertiesChanged = False + else: + if not quiet: + E5MessageBox.critical( + self.ui, + self.tr("Read Debugger Properties"), + self.tr( + "<p>The project debugger properties file" + " <b>{0}</b> could not be read.</p>").format(fn)) + + @pyqtSlot() + def __writeDebugProperties(self, quiet=False): + """ + Private method to write the project debugger properties file (.edj). @param quiet flag indicating quiet operations. If this flag is true, no errors are reported. @@ -1182,87 +1239,40 @@ if not quiet: E5MessageBox.critical( self.ui, - self.tr("Read debugger properties"), + self.tr("Save Debugger Properties"), self.tr("Please save the project first.")) return - + fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), '{0}.e4d'.format(fn)) - - f = QFile(fn) - if f.open(QIODevice.ReadOnly): - from E5XML.DebuggerPropertiesReader import DebuggerPropertiesReader - reader = DebuggerPropertiesReader(f, self) - reader.readXML(quiet=quiet) - f.close() - self.debugPropertiesLoaded = True - self.debugPropertiesChanged = False - else: - if not quiet: - E5MessageBox.critical( - self.ui, - self.tr("Read debugger properties"), - self.tr( - "<p>The project debugger properties file <b>{0}</b>" - " could not be read.</p>").format(fn)) - - @pyqtSlot() - def __writeDebugProperties(self, quiet=False): - """ - Private method to write the project debugger properties file (.e4d). - - @param quiet flag indicating quiet operations. - If this flag is true, no errors are reported. - """ - if self.pfile is None: - if not quiet: - E5MessageBox.critical( - self.ui, - self.tr("Save debugger properties"), - self.tr("Please save the project first.")) - return - - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), '{0}.e4d'.format(fn)) - - f = QFile(fn) - if f.open(QIODevice.WriteOnly): - from E5XML.DebuggerPropertiesWriter import DebuggerPropertiesWriter - DebuggerPropertiesWriter( - f, os.path.splitext(os.path.basename(fn))[0]).writeXML() - f.close() - self.debugPropertiesChanged = False - else: - if not quiet: - E5MessageBox.critical( - self.ui, - self.tr("Save debugger properties"), - self.tr( - "<p>The project debugger properties file <b>{0}</b>" - " could not be written.</p>").format(fn)) - + fn = os.path.join(self.getProjectManagementDir(), '{0}.edj'.format(fn)) + + with E5OverrideCursor(): + self.__debuggerPropertiesFile.writeFile(fn) + def __deleteDebugProperties(self): """ - Private method to delete the project debugger properties file (.e4d). + Private method to delete the project debugger properties file + (.edj or .e4d). """ if self.pfile is None: E5MessageBox.critical( self.ui, - self.tr("Delete debugger properties"), + self.tr("Delete Debugger Properties"), self.tr("Please save the project first.")) return fname, ext = os.path.splitext(os.path.basename(self.pfile)) - for fn in [os.path.join(self.getProjectManagementDir(), - "{0}.e4d".format(fname))]: + for ext in (".edj", ".e4d"): + fn = os.path.join(self.getProjectManagementDir(), + "{0}{1}".format(fname, ext)) if os.path.exists(fn): try: os.remove(fn) except OSError: E5MessageBox.critical( self.ui, - self.tr("Delete debugger properties"), + self.tr("Delete Debugger Properties"), self.tr( "<p>The project debugger properties file" " <b>{0}</b> could not be deleted.</p>") @@ -2187,7 +2197,7 @@ def removeDirectory(self, dn): """ - Public slot to remove a directory from the project. + Public method to remove a directory from the project. The directory is not deleted from the project directory. @@ -2216,7 +2226,7 @@ def deleteFile(self, fn): """ - Public slot to delete a file from the project directory. + Public method to delete a file from the project directory. @param fn filename to be deleted from the project @return flag indicating success (boolean) @@ -2260,7 +2270,7 @@ def deleteDirectory(self, dn): """ - Public slot to delete a directory from the project directory. + Public method to delete a directory from the project directory. @param dn directory name to be removed from the project @return flag indicating success (boolean) @@ -2396,7 +2406,7 @@ self.ppath, self.pdata["MAINSCRIPT"]) else: ms = self.pdata["MAINSCRIPT"] - os.makedirs(os.path.dirname(ms)) + os.makedirs(os.path.dirname(ms), exist_ok=True) with open(ms, "w"): pass self.appendFile(ms, True) @@ -2408,7 +2418,7 @@ mf = os.path.join(self.ppath, mf) else: mf = os.path.join(self.ppath, Project.DefaultMakefile) - os.makedirs(os.path.dirname(mf)) + os.makedirs(os.path.dirname(mf), exist_ok=True) with open(mf, "w"): pass self.appendFile(mf) @@ -2417,12 +2427,12 @@ if not self.translationsRoot.endswith(os.sep): tpd = os.path.dirname(tpd) if not os.path.isdir(tpd): - os.makedirs(tpd) + os.makedirs(tpd, exist_ok=True) if self.pdata["TRANSLATIONSBINPATH"]: tpd = os.path.join( self.ppath, self.pdata["TRANSLATIONSBINPATH"]) if not os.path.isdir(tpd): - os.makedirs(tpd) + os.makedirs(tpd, exist_ok=True) # create management directory if not present self.createProjectManagementDir() @@ -2940,7 +2950,7 @@ self.tr("Open project"), Preferences.getMultiProject("Workspace") or Utilities.getHomeDir(), - self.tr("Project Files (*.e4p)")) + self.tr("Project Files (*.epj);;XML Project Files (*.e4p)")) QApplication.processEvents() @@ -3110,6 +3120,9 @@ """ if self.isDirty(): if len(self.pfile) > 0: + if self.pfile.endswith(".e4p"): + self.pfile = self.pfile.replace(".e4p", ".epj") + self.__syncRecent() ok = self.__writeProject() else: ok = self.saveProjectAs() @@ -3125,7 +3138,7 @@ @return flag indicating success (boolean) """ - defaultFilter = self.tr("Project Files (*.e4p)") + defaultFilter = self.tr("Project Files (*.epj)") if self.ppath: defaultPath = self.ppath else: @@ -3137,7 +3150,8 @@ self.parent(), self.tr("Save project as"), defaultPath, - self.tr("Project Files (*.e4p)"), + self.tr("Project Files (*.epj);;" + "XML Project Files (*.e4p)"), defaultFilter, E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) @@ -4031,9 +4045,9 @@ self.filetypesAct = E5Action( self.tr('Filetype Associations'), self.tr('Filetype Associations...'), 0, 0, - self, 'project_filetype_associatios') + self, 'project_filetype_associations') self.filetypesAct.setStatusTip( - self.tr('Show the project filetype associations')) + self.tr('Show the project file type associations')) self.filetypesAct.setWhatsThis(self.tr( """<b>Filetype Associations...</b>""" """<p>This shows a dialog to edit the file type associations of""" @@ -4049,7 +4063,7 @@ self.lexersAct = E5Action( self.tr('Lexer Associations'), self.tr('Lexer Associations...'), 0, 0, - self, 'project_lexer_associatios') + self, 'project_lexer_associations') self.lexersAct.setStatusTip(self.tr( 'Show the project lexer associations (overriding defaults)')) self.lexersAct.setWhatsThis(self.tr(