From 2e590b742fa00f37d4db3e035b9a0bc445398c01 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 3 Nov 2022 23:47:51 +0100 Subject: [PATCH] Move new project tool to core tools (resolves #1152) --- novelwriter/core/__init__.py | 3 +- novelwriter/core/coretools.py | 215 ++++++++++++++++- novelwriter/core/project.py | 219 ++---------------- novelwriter/guimain.py | 30 +-- ...nwx => coreTools_NewCustomA_nwProject.nwx} | 4 +- ...nwx => coreTools_NewCustomB_nwProject.nwx} | 4 +- ...nwx => coreTools_NewMinimal_nwProject.nwx} | 8 +- tests/test_core/test_core_coretools.py | 156 ++++++++++++- tests/test_core/test_core_project.py | 210 +---------------- tests/tools.py | 10 +- 10 files changed, 398 insertions(+), 461 deletions(-) rename tests/reference/{coreProject_NewCustomA_nwProject.nwx => coreTools_NewCustomA_nwProject.nwx} (99%) rename tests/reference/{coreProject_NewCustomB_nwProject.nwx => coreTools_NewCustomB_nwProject.nwx} (98%) rename tests/reference/{coreProject_NewMinimal_nwProject.nwx => coreTools_NewMinimal_nwProject.nwx} (96%) diff --git a/novelwriter/core/__init__.py b/novelwriter/core/__init__.py index 684ce53d..b535785b 100644 --- a/novelwriter/core/__init__.py +++ b/novelwriter/core/__init__.py @@ -19,7 +19,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -from novelwriter.core.coretools import DocMerger, DocSplitter +from novelwriter.core.coretools import DocMerger, DocSplitter, ProjectBuilder from novelwriter.core.document import NWDoc from novelwriter.core.index import countWords from novelwriter.core.project import NWProject @@ -31,6 +31,7 @@ from novelwriter.core.tomd import ToMarkdown __all__ = [ "DocMerger", "DocSplitter", + "ProjectBuilder", "countWords", "NWDoc", "NWProject", diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index 95fec897..0c24fc29 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -24,15 +24,30 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import os +import shutil import logging +import novelwriter -from novelwriter.common import minmax +from time import time +from functools import partial + +from PyQt5.QtCore import QCoreApplication + +from novelwriter.enum import nwAlert +from novelwriter.common import minmax, simplified +from novelwriter.constants import nwItemClass +from novelwriter.core.project import NWProject from novelwriter.core.document import NWDoc logger = logging.getLogger(__name__) class DocMerger: + """Document tool for merging a set of documents into a single new + document. The parameters are defined by the user using the + GuiDocMerge dialog. + """ def __init__(self, theProject): @@ -122,6 +137,10 @@ class DocMerger: class DocSplitter: + """Document tool for splitting a document into a set of new + documents. The parameters are defined by the user using the + GuiDocSplit dialog. + """ def __init__(self, theProject, sHandle): @@ -242,3 +261,197 @@ class DocSplitter: return # END Class DocSplitter + + +class ProjectBuilder: + """A class to build a new project from a set of user-defined + parameter provided by the New Projecty Wizard. + """ + + def __init__(self, mainGui): + + self.mainGui = mainGui + self.mainConf = novelwriter.CONFIG + + self.tr = partial(QCoreApplication.translate, "NWProject") + + return + + ## + # Methods + ## + + def buildProject(self, data): + """Build a project from a data dictionary of specifications + provided by the wizard. + """ + if not isinstance(data, dict): + logger.error("Invalid call to newProject function") + return False + + popMinimal = data.get("popMinimal", True) + popCustom = data.get("popCustom", False) + popSample = data.get("popSample", False) + + # Check if we're extracting the sample project. This is handled + # differently as it isn't actually a new project, so we forward + # this to another function and return here. + if popSample: + return self._extractSampleProject(data) + + projPath = data.get("projPath", None) + if projPath is None: + logger.error("No project path set for the new project") + return False + + project = NWProject(self.mainGui) + if not project.setProjectPath(projPath, newProject=True): + return False + + if not project.storage.openProjectInPlace(projPath): + return False + + lblNewProject = self.tr("New Project") + lblNewChapter = self.tr("New Chapter") + lblNewScene = self.tr("New Scene") + lblTitlePage = self.tr("Title Page") + lblByAuthors = self.tr("By") + + # Settings + projName = data.get("projName", lblNewProject) + projTitle = data.get("projTitle", lblNewProject) + projAuthors = data.get("projAuthors", "") + + project.data.setName(projName) + project.data.setTitle(projTitle) + project.data.setAuthors(projAuthors) + project.setDefaultStatusImport() + project._projOpened = int(time()) + + # Add Root Folders + hNovelRoot = project.newRoot(nwItemClass.NOVEL) + hTitlePage = project.newFile(lblTitlePage, hNovelRoot) + novelTitle = project.data.title if project.data.title else project.data.name + + titlePage = f"#! {novelTitle}\n\n" + if project.data.authors: + titlePage += f">> {lblByAuthors} {project.getFormattedAuthors()} <<\n\n" + + aDoc = NWDoc(project, hTitlePage) + aDoc.writeDocument(titlePage) + + if popMinimal: + # Creating a minimal project with a few root folders and a + # single chapter with a single scene. + hChapter = project.newFile(lblNewChapter, hNovelRoot) + aDoc = NWDoc(project, hChapter) + aDoc.writeDocument(f"## {lblNewChapter}\n\n") + + hScene = project.newFile(lblNewScene, hChapter) + aDoc = NWDoc(project, hScene) + aDoc.writeDocument(f"### {lblNewScene}\n\n") + + project.newRoot(nwItemClass.PLOT) + project.newRoot(nwItemClass.CHARACTER) + project.newRoot(nwItemClass.WORLD) + project.newRoot(nwItemClass.ARCHIVE) + + project.saveProject() + project.closeProject() + + elif popCustom: + # Create a project structure based on selected root folders + # and a number of chapters and scenes selected in the + # wizard's custom page. + + # Create chapters and scenes + numChapters = data.get("numChapters", 0) + numScenes = data.get("numScenes", 0) + + chSynop = self.tr("Summary of the chapter.") + scSynop = self.tr("Summary of the scene.") + + # Create chapters + if numChapters > 0: + for ch in range(numChapters): + chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}") + cHandle = project.newFile(chTitle, hNovelRoot) + aDoc = NWDoc(project, cHandle) + aDoc.writeDocument(f"## {chTitle}\n\n% Synopsis: {chSynop}\n\n") + + # Create chapter scenes + if numScenes > 0: + for sc in range(numScenes): + scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}") + sHandle = project.newFile(scTitle, cHandle) + aDoc = NWDoc(project, sHandle) + aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n") + + # Create scenes (no chapters) + elif numScenes > 0: + for sc in range(numScenes): + scTitle = self.tr("Scene {0}").format(f"{sc+1:d}") + sHandle = project.newFile(scTitle, hNovelRoot) + aDoc = NWDoc(project, sHandle) + aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n") + + # Create notes folders + noteTitles = { + nwItemClass.PLOT: self.tr("Main Plot"), + nwItemClass.CHARACTER: self.tr("Protagonist"), + nwItemClass.WORLD: self.tr("Main Location"), + } + + addNotes = data.get("addNotes", False) + for newRoot in data.get("addRoots", []): + if newRoot in nwItemClass: + rHandle = project.newRoot(newRoot) + if addNotes: + aHandle = project.newFile(noteTitles[newRoot], rHandle) + ntTag = simplified(noteTitles[newRoot]).replace(" ", "") + aDoc = NWDoc(project, aHandle) + aDoc.writeDocument(f"# {noteTitles[newRoot]}\n\n@tag: {ntTag}\n\n") + + # Also add the archive and trash folders + project.newRoot(nwItemClass.ARCHIVE) + project.trashFolder() + + project.saveProject() + project.closeProject() + + return True + + ## + # Internal Functions + ## + + def _extractSampleProject(self, data): + """Make a copy of the sample project by extracting the + sample.zip file to the new path. + """ + projPath = data.get("projPath", None) + if projPath is None: + logger.error("No project path set for the example project") + return False + + pkgSample = os.path.join(self.mainConf.assetPath, "sample.zip") + if os.path.isfile(pkgSample): + try: + shutil.unpack_archive(pkgSample, projPath) + except Exception as exc: + self.mainGui.makeAlert(self.tr( + "Failed to create a new example project." + ), nwAlert.ERROR, exception=exc) + return False + + else: + self.mainGui.makeAlert(self.tr( + "Failed to create a new example project. " + "Could not find the necessary files. " + "They seem to be missing from this installation." + ), nwAlert.ERROR) + return False + + return True + +# END Class ProjectBuilder diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 709310a2..1714cfd2 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -264,150 +264,6 @@ class NWProject(QObject): return - def newProject(self, projData): - """Create a new project by populating the project tree with a - few starter items. - """ - if not isinstance(projData, dict): - logger.error("Invalid call to newProject function") - return False - - popMinimal = projData.get("popMinimal", True) - popCustom = projData.get("popCustom", False) - popSample = projData.get("popSample", False) - - # Check if we're extracting the sample project. This is handled - # differently as it isn't actually a new project, so we forward - # this to another function and return here. - if popSample: - return self.extractSampleProject(projData) - - # Project Settings - projPath = projData.get("projPath", None) - projName = projData.get("projName", self.tr("New Project")) - projTitle = projData.get("projTitle", "") - projAuthors = projData.get("projAuthors", "") - - if projPath is None: - logger.error("No project path set for the new project") - return False - - self.clearProject() - - self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100)) - self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0)) - self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0)) - self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0)) - - self._data.itemImport.write(None, self.tr("New"), (100, 100, 100)) - self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0)) - self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0)) - self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0)) - - if not self.setProjectPath(projPath, newProject=True): - return False - - self._storage.openProjectInPlace(self.projPath) - - self._data.setName(projName) - self._data.setTitle(projTitle) - self._data.setAuthors(projAuthors) - - hNovelRoot = self.newRoot(nwItemClass.NOVEL) - hTitlePage = self.newFile(self.tr("Title Page"), hNovelRoot) - - titlePage = "#! %s\n\n" % ( - self._data.title if self._data.title else self._data.name - ) - if self._data.authors: - titlePage = "%s>> %s %s <<\n" % ( - titlePage, self.tr("By"), self.getFormattedAuthors() - ) - - aDoc = NWDoc(self, hTitlePage) - aDoc.writeDocument(titlePage) - - if popMinimal: - # Creating a minimal project with a few root folders and a - # single chapter with a single scene. - hChapter = self.newFile(self.tr("New Chapter"), hNovelRoot) - aDoc = NWDoc(self, hChapter) - aDoc.writeDocument("## %s\n\n" % self.tr("New Chapter")) - - hScene = self.newFile(self.tr("New Scene"), hChapter) - aDoc = NWDoc(self, hScene) - aDoc.writeDocument("### %s\n\n" % self.tr("New Scene")) - - self.newRoot(nwItemClass.PLOT) - self.newRoot(nwItemClass.CHARACTER) - self.newRoot(nwItemClass.WORLD) - self.newRoot(nwItemClass.ARCHIVE) - - elif popCustom: - # Create a project structure based on selected root folders - # and a number of chapters and scenes selected in the - # wizard's custom page. - - # Create chapters and scenes - numChapters = projData.get("numChapters", 0) - numScenes = projData.get("numScenes", 0) - - chSynop = self.tr("Summary of the chapter.") - scSynop = self.tr("Summary of the scene.") - - # Create chapters - if numChapters > 0: - for ch in range(numChapters): - chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}") - cHandle = self.newFile(chTitle, hNovelRoot) - aDoc = NWDoc(self, cHandle) - aDoc.writeDocument(f"## {chTitle}\n\n% Synopsis: {chSynop}\n\n") - - # Create chapter scenes - if numScenes > 0: - for sc in range(numScenes): - scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}") - sHandle = self.newFile(scTitle, cHandle) - aDoc = NWDoc(self, sHandle) - aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n") - - # Create scenes (no chapters) - elif numScenes > 0: - for sc in range(numScenes): - scTitle = self.tr("Scene {0}").format(f"{sc+1:d}") - sHandle = self.newFile(scTitle, hNovelRoot) - aDoc = NWDoc(self, sHandle) - aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n") - - # Create notes folders - noteTitles = { - nwItemClass.PLOT: self.tr("Main Plot"), - nwItemClass.CHARACTER: self.tr("Protagonist"), - nwItemClass.WORLD: self.tr("Main Location"), - } - - addNotes = projData.get("addNotes", False) - for newRoot in projData.get("addRoots", []): - if newRoot in nwItemClass: - rHandle = self.newRoot(newRoot) - if addNotes: - aHandle = self.newFile(noteTitles[newRoot], rHandle) - ntTag = simplified(noteTitles[newRoot]).replace(" ", "") - aDoc = NWDoc(self, aHandle) - aDoc.writeDocument(f"# {noteTitles[newRoot]}\n\n@tag: {ntTag}\n\n") - - # Also add the archive and trash folders - self.newRoot(nwItemClass.ARCHIVE) - self.trashFolder() - - # Finalise - if popCustom or popMinimal: - self._projOpened = time() - self.setProjectChanged(True) - self.saveProject(autoSave=True) - - return True - def openProject(self, fileName, overrideLock=False): """Open the project file provided. If it doesn't exist, assume it is a folder and look for the file within it. If successful, @@ -680,6 +536,19 @@ class NWProject(QObject): return True + def setDefaultStatusImport(self): + """Set the default status and importance values. + """ + self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100)) + self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0)) + self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0)) + self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0)) + self._data.itemImport.write(None, self.tr("New"), (100, 100, 100)) + self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0)) + self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0)) + self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0)) + return + ## # Zip/Unzip Project ## @@ -753,68 +622,6 @@ class NWProject(QObject): return True - def extractSampleProject(self, projData): - """Make a copy of the sample project. - First, look for the sample.zip file in the assets folder and - unpack it. If it doesn't exist, try to copy the content of the - sample folder to the new project path. If neither exits, error. - """ - projPath = projData.get("projPath", None) - if projPath is None: - logger.error("No project path set for the example project") - return False - - srcSample = os.path.abspath(os.path.join(self.mainConf.appRoot, "sample")) - pkgSample = os.path.join(self.mainConf.assetPath, "sample.zip") - - isSuccess = False - if os.path.isfile(pkgSample): - - self.setProjectPath(projPath, newProject=True) - try: - shutil.unpack_archive(pkgSample, projPath) - isSuccess = True - except Exception as exc: - self.mainGui.makeAlert(self.tr( - "Failed to create a new example project." - ), nwAlert.ERROR, exception=exc) - - elif os.path.isdir(srcSample): - - self.setProjectPath(projPath, newProject=True) - try: - srcProj = os.path.join(srcSample, nwFiles.PROJ_FILE) - dstProj = os.path.join(projPath, nwFiles.PROJ_FILE) - shutil.copyfile(srcProj, dstProj) - - srcContent = os.path.join(srcSample, "content") - dstContent = os.path.join(projPath, "content") - for srcFile in os.listdir(srcContent): - srcDoc = os.path.join(srcContent, srcFile) - dstDoc = os.path.join(dstContent, srcFile) - shutil.copyfile(srcDoc, dstDoc) - - isSuccess = True - - except Exception as exc: - self.mainGui.makeAlert(self.tr( - "Failed to create a new example project." - ), nwAlert.ERROR, exception=exc) - - else: - self.mainGui.makeAlert(self.tr( - "Failed to create a new example project. " - "Could not find the necessary files. " - "They seem to be missing from this installation." - ), nwAlert.ERROR) - - if isSuccess: - self.clearProject() - self.mainGui.openProject(projPath) - self.mainGui.rebuildIndex() - - return isSuccess - ## # Setters ## diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 516cec42..c0745460 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -50,9 +50,9 @@ from novelwriter.dialogs import ( from novelwriter.tools import ( GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats ) -from novelwriter.core import NWProject +from novelwriter.core import NWProject, ProjectBuilder from novelwriter.enum import ( - nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwState, nwView + nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwView ) from novelwriter.common import getGuiItem, hexToInt from novelwriter.constants import nwFiles @@ -365,30 +365,10 @@ class GuiMain(QMainWindow): return False logger.info("Creating new project") - if self.theProject.newProject(projData): - - self.hasProject = True - self.idleRefTime = time() - self.idleTime = 0.0 - - self.rebuildTrees() - self.saveProject() - - self.docEditor.setDictionaries() - self.projView.openProjectTasks() - self.novelView.openProjectTasks() - self.outlineView.openProjectTasks() - self.rebuildIndex(beQuiet=True) - - self.mainStatus.setRefTime(self.theProject.projOpened) - self.mainStatus.setProjectStatus(nwState.GOOD) - self.mainStatus.setDocumentStatus(nwState.NONE) - self.mainStatus.setStatus(self.tr("New project created ...")) - - self._updateWindowTitle(self.theProject.data.name) - + nwProject = ProjectBuilder(self) + if nwProject.buildProject(projData): + self.openProject(projPath) else: - self.theProject.clearProject() return False return True diff --git a/tests/reference/coreProject_NewCustomA_nwProject.nwx b/tests/reference/coreTools_NewCustomA_nwProject.nwx similarity index 99% rename from tests/reference/coreProject_NewCustomA_nwProject.nwx rename to tests/reference/coreTools_NewCustomA_nwProject.nwx index 76509948..f6314c1a 100644 --- a/tests/reference/coreProject_NewCustomA_nwProject.nwx +++ b/tests/reference/coreTools_NewCustomA_nwProject.nwx @@ -1,12 +1,12 @@ - + Test Custom Test Novel Jane Doe John Doh 1 - 1 + 0 0 diff --git a/tests/reference/coreProject_NewCustomB_nwProject.nwx b/tests/reference/coreTools_NewCustomB_nwProject.nwx similarity index 98% rename from tests/reference/coreProject_NewCustomB_nwProject.nwx rename to tests/reference/coreTools_NewCustomB_nwProject.nwx index afd7627c..23e0c509 100644 --- a/tests/reference/coreProject_NewCustomB_nwProject.nwx +++ b/tests/reference/coreTools_NewCustomB_nwProject.nwx @@ -1,12 +1,12 @@ - + Test Custom Test Novel Jane Doe John Doh 1 - 1 + 0 0 diff --git a/tests/reference/coreProject_NewMinimal_nwProject.nwx b/tests/reference/coreTools_NewMinimal_nwProject.nwx similarity index 96% rename from tests/reference/coreProject_NewMinimal_nwProject.nwx rename to tests/reference/coreTools_NewMinimal_nwProject.nwx index d43aee14..0f2388f5 100644 --- a/tests/reference/coreProject_NewMinimal_nwProject.nwx +++ b/tests/reference/coreTools_NewMinimal_nwProject.nwx @@ -1,10 +1,10 @@ - + New Project - None - 2 - 1 + New Project + 1 + 0 0 diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py index 4bf4c407..f98e32f1 100644 --- a/tests/test_core/test_core_coretools.py +++ b/tests/test_core/test_core_coretools.py @@ -23,13 +23,15 @@ import os import pytest from shutil import copyfile +from zipfile import ZipFile from mock import causeOSError -from tools import C, buildTestProject, cmpFiles +from tools import C, buildTestProject, cmpFiles, XML_IGNORE +from novelwriter.constants import nwItemClass from novelwriter.core.project import NWProject from novelwriter.core.document import NWDoc -from novelwriter.core.coretools import DocMerger, DocSplitter +from novelwriter.core.coretools import DocMerger, DocSplitter, ProjectBuilder @pytest.mark.core @@ -259,3 +261,153 @@ def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, mock theProject.saveProject() # END Test testCoreTools_DocSplitter + + +@pytest.mark.core +def testCoreTools_NewMinimal(fncDir, outDir, refDir, mockGUI, mockRnd): + """Create a new project from a project wizard dictionary. With + default setting, creating a Minimal project. + """ + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreTools_NewMinimal_nwProject.nwx") + compFile = os.path.join(refDir, "coreTools_NewMinimal_nwProject.nwx") + + projBuild = ProjectBuilder(mockGUI) + + # Setting no data should fail + assert projBuild.buildProject({}) is False + + # Wrong type should also fail + assert projBuild.buildProject("stuff") is False + + # Try again with a proper path + assert projBuild.buildProject({"projPath": fncDir}) is True + + # Creating the project once more should fail + assert projBuild.buildProject({"projPath": fncDir}) is False + + # Save and close + copyfile(projFile, testFile) + assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) + +# END Test testCoreTools_NewMinimal + + +@pytest.mark.core +def testCoreTools_NewCustomA(fncDir, outDir, refDir, mockGUI, mockRnd): + """Create a new project from a project wizard dictionary. + Custom type with chapters and scenes. + """ + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreTools_NewCustomA_nwProject.nwx") + compFile = os.path.join(refDir, "coreTools_NewCustomA_nwProject.nwx") + + projData = { + "projName": "Test Custom", + "projTitle": "Test Novel", + "projAuthors": "Jane Doe\nJohn Doh\n", + "projPath": fncDir, + "popSample": False, + "popMinimal": False, + "popCustom": True, + "addRoots": [ + nwItemClass.PLOT, + nwItemClass.CHARACTER, + nwItemClass.WORLD, + ], + "addNotes": True, + "numChapters": 3, + "numScenes": 3, + } + + projBuild = ProjectBuilder(mockGUI) + assert projBuild.buildProject(projData) is True + + copyfile(projFile, testFile) + assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) + +# END Test testCoreTools_NewCustomA + + +@pytest.mark.core +def testCoreTools_NewCustomB(fncDir, outDir, refDir, mockGUI, mockRnd): + """Create a new project from a project wizard dictionary. + Custom type without chapters, but with scenes. + """ + projFile = os.path.join(fncDir, "nwProject.nwx") + testFile = os.path.join(outDir, "coreTools_NewCustomB_nwProject.nwx") + compFile = os.path.join(refDir, "coreTools_NewCustomB_nwProject.nwx") + + projData = { + "projName": "Test Custom", + "projTitle": "Test Novel", + "projAuthors": "Jane Doe\nJohn Doh\n", + "projPath": fncDir, + "popSample": False, + "popMinimal": False, + "popCustom": True, + "addRoots": [ + nwItemClass.PLOT, + nwItemClass.CHARACTER, + nwItemClass.WORLD, + ], + "addNotes": True, + "numChapters": 0, + "numScenes": 6, + } + + projBuild = ProjectBuilder(mockGUI) + assert projBuild.buildProject(projData) is True + + copyfile(projFile, testFile) + assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) + +# END Test testCoreTools_NewCustomB + + +@pytest.mark.core +def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir): + """Check that we can create a new project can be created from the + provided sample project via a zip file. + """ + projData = { + "projName": "Test Sample", + "projTitle": "Test Novel", + "projAuthors": "Jane Doe\nJohn Doh\n", + "projPath": fncDir, + "popSample": True, + "popMinimal": False, + "popCustom": False, + } + + projBuild = ProjectBuilder(mockGUI) + + # No path set + assert projBuild.buildProject({"popSample": True}) is False + + # Force the lookup path for assets to our temp folder + srcSample = os.path.abspath(os.path.join(tmpConf.appRoot, "sample")) + dstSample = os.path.join(tmpDir, "sample.zip") + tmpConf.assetPath = tmpDir + + # Cannot extract when the zip does not exist + assert projBuild.buildProject(projData) is False + + # Create and open a defective zip file + with open(dstSample, mode="w+") as outFile: + outFile.write("foo") + + assert projBuild.buildProject(projData) is False + os.unlink(dstSample) + + # Create a real zip file, and unpack it + with ZipFile(dstSample, "w") as zipObj: + zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx") + for docFile in os.listdir(os.path.join(srcSample, "content")): + srcDoc = os.path.join(srcSample, "content", docFile) + zipObj.write(srcDoc, "content/"+docFile) + + assert projBuild.buildProject(projData) is True + os.unlink(dstSample) + +# END Test testCoreTools_NewSample diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index 02ce6c27..9d7b12e5 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -27,7 +27,7 @@ from shutil import copyfile from zipfile import ZipFile from mock import causeOSError -from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C +from tools import C, cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout from novelwriter.common import formatTimeStamp @@ -40,214 +40,6 @@ from novelwriter.core.document import NWDoc from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState -@pytest.mark.core -def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI, mockRnd): - """Create a new project from a project wizard dictionary. With - default setting, creating a Minimal project. - """ - projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx") - - theProject = NWProject(mockGUI) - - # Setting no data should fail - assert theProject.newProject({}) is False - - # Wrong type should also fail - assert theProject.newProject("stuff") is False - - # Try again with a proper path - assert theProject.newProject({"projPath": fncDir}) is True - assert theProject.saveProject() is True - assert theProject.closeProject() is True - - # Creating the project once more should fail - assert theProject.newProject({"projPath": fncDir}) is False - - # Open again - assert theProject.openProject(projFile) is True - - # Save and close - assert theProject.saveProject() is True - assert theProject.closeProject() is True - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) - assert theProject.projChanged is False - - # Open a second time - assert theProject.openProject(projFile) is True - assert theProject.openProject(projFile) is False - assert theProject.openProject(projFile, overrideLock=True) is True - assert theProject.saveProject() is True - assert theProject.closeProject() is True - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) - -# END Test testCoreProject_NewMinimal - - -@pytest.mark.core -def testCoreProject_NewCustomA(fncDir, outDir, refDir, mockGUI, mockRnd): - """Create a new project from a project wizard dictionary. - Custom type with chapters and scenes. - """ - projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_NewCustomA_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_NewCustomA_nwProject.nwx") - - projData = { - "projName": "Test Custom", - "projTitle": "Test Novel", - "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": fncDir, - "popSample": False, - "popMinimal": False, - "popCustom": True, - "addRoots": [ - nwItemClass.PLOT, - nwItemClass.CHARACTER, - nwItemClass.WORLD, - ], - "addNotes": True, - "numChapters": 3, - "numScenes": 3, - } - theProject = NWProject(mockGUI) - - assert theProject.newProject(projData) is True - assert theProject.saveProject() is True - assert theProject.closeProject() is True - - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) - -# END Test testCoreProject_NewCustomA - - -@pytest.mark.core -def testCoreProject_NewCustomB(fncDir, outDir, refDir, mockGUI, mockRnd): - """Create a new project from a project wizard dictionary. - Custom type without chapters, but with scenes. - """ - projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_NewCustomB_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_NewCustomB_nwProject.nwx") - - projData = { - "projName": "Test Custom", - "projTitle": "Test Novel", - "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": fncDir, - "popSample": False, - "popMinimal": False, - "popCustom": True, - "addRoots": [ - nwItemClass.PLOT, - nwItemClass.CHARACTER, - nwItemClass.WORLD, - ], - "addNotes": True, - "numChapters": 0, - "numScenes": 6, - } - theProject = NWProject(mockGUI) - - assert theProject.newProject(projData) is True - assert theProject.saveProject() is True - assert theProject.closeProject() is True - - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) - -# END Test testCoreProject_NewCustomB - - -@pytest.mark.core -def testCoreProject_NewSampleA(fncDir, tmpConf, mockGUI, tmpDir): - """Check that we can create a new project can be created from the - provided sample project via a zip file. - """ - projData = { - "projName": "Test Sample", - "projTitle": "Test Novel", - "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": fncDir, - "popSample": True, - "popMinimal": False, - "popCustom": False, - } - theProject = NWProject(mockGUI) - - # Sample set, but no path - assert not theProject.newProject({"popSample": True}) - - # Force the lookup path for assets to our temp folder - srcSample = os.path.abspath(os.path.join(tmpConf.appRoot, "sample")) - dstSample = os.path.join(tmpDir, "sample.zip") - tmpConf.assetPath = tmpDir - - # Create and open a defective zip file - with open(dstSample, mode="w+") as outFile: - outFile.write("foo") - - assert not theProject.newProject(projData) - os.unlink(dstSample) - - # Create a real zip file, and unpack it - with ZipFile(dstSample, "w") as zipObj: - zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx") - for docFile in os.listdir(os.path.join(srcSample, "content")): - srcDoc = os.path.join(srcSample, "content", docFile) - zipObj.write(srcDoc, "content/"+docFile) - - assert theProject.newProject(projData) is True - assert theProject.openProject(fncDir) is True - assert theProject.data.name == "Sample Project" - assert theProject.saveProject() is True - assert theProject.closeProject() is True - os.unlink(dstSample) - -# END Test testCoreProject_NewSampleA - - -@pytest.mark.core -def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, mockGUI, tmpDir): - """Check that we can create a new project can be created from the - provided sample project folder. - """ - projData = { - "projName": "Test Sample", - "projTitle": "Test Novel", - "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": fncDir, - "popSample": True, - "popMinimal": False, - "popCustom": False, - } - theProject = NWProject(mockGUI) - - # Make sure we do not pick up the novelwriter/assets/sample.zip file - tmpConf.assetPath = tmpDir - - # Set a fake project file name - monkeypatch.setattr(nwFiles, "PROJ_FILE", "nothing.nwx") - assert not theProject.newProject(projData) - - monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx") - assert theProject.newProject(projData) is True - assert theProject.openProject(fncDir) is True - assert theProject.data.name == "Sample Project" - assert theProject.saveProject() is True - assert theProject.closeProject() is True - - # Misdirect the appRoot path so neither is possible - tmpConf.appRoot = tmpDir - assert not theProject.newProject(projData) - -# END Test testCoreProject_NewSampleB - - @pytest.mark.core def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd): """Check that new root folders can be added to the project. diff --git a/tests/tools.py b/tests/tools.py index 9a2e4dd8..5094fa62 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -168,15 +168,7 @@ def buildTestProject(theObject, projPath): theProject.clearProject() theProject.setProjectPath(projPath, newProject=True) theProject.storage.openProjectInPlace(theProject.projPath) - - theProject.data.itemStatus.write(None, "New", (100, 100, 100)) - theProject.data.itemStatus.write(None, "Note", (200, 50, 0)) - theProject.data.itemStatus.write(None, "Draft", (200, 150, 0)) - theProject.data.itemStatus.write(None, "Finished", (50, 200, 0)) - theProject.data.itemImport.write(None, "New", (100, 100, 100)) - theProject.data.itemImport.write(None, "Minor", (200, 50, 0)) - theProject.data.itemImport.write(None, "Major", (200, 150, 0)) - theProject.data.itemImport.write(None, "Main", (50, 200, 0)) + theProject.setDefaultStatusImport() theProject.data.setName("New Project") theProject.data.setTitle("New Novel")