Allow creating project from other projects (#1680)

This commit is contained in:
Veronica Berglyd Olsen
2024-02-01 00:24:16 +01:00
committed by GitHub
2 changed files with 116 additions and 13 deletions
+52 -6
View File
@@ -36,8 +36,8 @@ from functools import partial
from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax, simplified
from novelwriter.constants import nwItemClass
from novelwriter.common import isHandle, minmax, simplified
from novelwriter.constants import nwFiles, nwItemClass
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
from novelwriter.core.storage import NWStorageCreate
@@ -330,9 +330,8 @@ class ProjectBuilder:
self._path = Path(path).resolve()
if data.get("sample", False):
return self._extractSampleProject(self._path)
elif data.get("template"): # pragma: no cover
# Not implemented yet
return True
elif data.get("template"):
return self._copyProject(self._path, data)
else:
return self._buildAndPopulate(self._path, data)
SHARED.error("A project path is required.")
@@ -348,7 +347,7 @@ class ProjectBuilder:
status = project.storage.createNewProject(path)
if status == NWStorageCreate.NOT_EMPTY:
SHARED.error(self.tr(
"A project already exists in that location. "
"The target folder is not empty. "
"Please choose another folder."
))
return False
@@ -454,6 +453,53 @@ class ProjectBuilder:
return True
def _copyProject(self, path: Path, data: dict) -> bool:
"""Copy an existing project content, but not the meta data, and
update new settings.
"""
source = data.get("template")
if not (isinstance(source, Path) and source.is_file()
and source.name == nwFiles.PROJ_FILE):
return False
logger.info("Copying project: %s", source)
if path.exists():
SHARED.error(self.tr(
"The target folder already exists. "
"Please choose another folder."
))
return False
# Begin copying
srcPath = source.parent
dstPath = path.resolve()
srcCont = srcPath / "content"
dstCont = dstPath / "content"
dstPath.mkdir(exist_ok=True)
dstCont.mkdir(exist_ok=True)
shutil.copy2(srcPath / nwFiles.PROJ_FILE, dstPath)
for contFile in srcCont.iterdir():
if contFile.is_file() and contFile.suffix == ".nwd" and isHandle(contFile.stem):
shutil.copy2(contFile, dstCont)
# Open the copied project and update settings
project = NWProject()
project.openProject(dstPath)
project.data.setUuid("") # Creates a fresh uuid
project.data.setName(data.get("name", "None"))
project.data.setAuthor(data.get("author", ""))
project.data.setLanguage(data.get("language", "en_GB"))
project.data.setSpellCheck(True)
project.data.setSpellLang(None)
project.data.setDoBackup(True)
project.data.setSaveCount(0)
project.data.setAutoCount(0)
project.data.setEditTime(0)
project.saveProject()
project.closeProject()
return True
def _extractSampleProject(self, path: Path) -> bool:
"""Make a copy of the sample project by extracting the
sample.zip file to the new path.
+64 -7
View File
@@ -31,7 +31,7 @@ from tools import C, NWD_IGNORE, buildTestProject, cmpFiles, XML_IGNORE
from mocked import causeOSError
from novelwriter import CONFIG
from novelwriter.constants import nwItemClass
from novelwriter.constants import nwFiles, nwItemClass
from novelwriter.core.project import NWProject
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter, ProjectBuilder
@@ -425,7 +425,7 @@ def testCoreTools_ProjectBuilderWrapper(monkeypatch, caplog, fncPath, mockGUI):
# Creating the project once more should fail
caplog.clear()
assert builder.buildProject({"path": fncPath}) is False
assert "A project already exists" in caplog.text
assert "The target folder is not empty." in caplog.text
# END Test testCoreTools_ProjectBuilderWrapper
@@ -466,7 +466,7 @@ def testCoreTools_ProjectBuilderA(monkeypatch, fncPath, tstPaths, mockRnd):
@pytest.mark.core
def testCoreTools_ProjectBuilderB(monkeypatch, fncPath, tstPaths, mockRnd):
def testCoreTools_ProjectBuilderB(monkeypatch, fncPath, tstPaths, mockRnd):
"""Create a new project from a project dictionary, without chapters."""
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
@@ -500,11 +500,68 @@ def testCoreTools_ProjectBuilderB(monkeypatch, fncPath, tstPaths, mockRnd):
# END Test testCoreTools_ProjectBuilderB
@pytest.mark.core
def testCoreTools_ProjectBuilderTemplate(monkeypatch, mockGUI, prjLipsum, fncPath):
"""Create a new project copied from existing project."""
srcPath = prjLipsum / nwFiles.PROJ_FILE
dstPath = fncPath / "lipsum"
data = {
"name": "Test Project",
"author": "Jane Doe",
"language": "en_US",
"path": dstPath,
"template": srcPath,
}
builder = ProjectBuilder()
# No path set
assert builder.buildProject({"template": srcPath}) is False
# No project at path
assert builder.buildProject({"path": fncPath, "template": fncPath}) is False
# Cannot copy to existing folder
assert builder.buildProject({"path": fncPath, "template": srcPath}) is False
# Copy project properly
assert builder.buildProject(data) is True
# Check Copy
# ==========
srcProject = NWProject()
srcProject.openProject(srcPath)
dstProject = NWProject()
dstProject.openProject(dstPath)
# UUID should be different
assert srcProject.data.uuid != dstProject.data.uuid
# Name should be different
assert srcProject.data.name == "Lorem Ipsum"
assert dstProject.data.name == "Test Project"
# Author should be different
assert srcProject.data.author == "lipsum.com"
assert dstProject.data.author == "Jane Doe"
# Language should be different
assert srcProject.data.language == "en_GB"
assert dstProject.data.language == "en_US"
# Counts should be more or less zeroed
assert dstProject.data.saveCount < 5
assert dstProject.data.autoCount < 5
assert dstProject.data.editTime < 10
# END Test testCoreTools_ProjectBuilderTemplate
@pytest.mark.core
def testCoreTools_ProjectBuilderSample(monkeypatch, mockGUI, fncPath, tstPaths):
"""Check that we can create a new project can be created from the
provided sample project via a zip file.
"""
"""Create a new sample project."""
data = {
"name": "Test Sample",
"author": "Jane Doe",
@@ -515,7 +572,7 @@ def testCoreTools_ProjectBuilderSample(monkeypatch, mockGUI, fncPath, tstPaths):
builder = ProjectBuilder()
# No path set
assert builder.buildProject({"popSample": True}) is False
assert builder.buildProject({"sample": True}) is False
# Force the lookup path for assets to our temp folder
srcSample = CONFIG._appRoot / "sample"