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")