Add document duplication tool and support in item and tree classes
This commit is contained in:
@@ -40,7 +40,7 @@ from novelwriter.constants import nwItemClass
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.project import NWProject
|
||||
|
||||
if TYPE_CHECKING:
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -76,7 +76,7 @@ class DocMerger:
|
||||
return
|
||||
|
||||
def newTargetDoc(self, srcHandle: str, docLabel: str) -> str | None:
|
||||
"""Create a barnd new target document based on a source handle
|
||||
"""Create a brand new target document based on a source handle
|
||||
and a new doc label. Calling this function resets the class.
|
||||
"""
|
||||
srcItem = self._project.tree[srcHandle]
|
||||
@@ -263,6 +263,49 @@ class DocSplitter:
|
||||
# END Class DocSplitter
|
||||
|
||||
|
||||
class DocDuplicator:
|
||||
"""A class that will duplicate all documents and folders starting
|
||||
from a given handle.
|
||||
"""
|
||||
|
||||
def __init__(self, project: NWProject) -> None:
|
||||
self._project = project
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def duplicate(self, items: list[str]) -> Iterable[tuple[str, str | None]]:
|
||||
"""Run through a list of items, duplicate them, and copy the
|
||||
text content if they are documents.
|
||||
"""
|
||||
if not items:
|
||||
return
|
||||
|
||||
nHandle = items[0]
|
||||
hMap: dict[str, str | None] = {t: None for t in items}
|
||||
for tHandle in items:
|
||||
newItem = self._project.tree.duplicate(tHandle)
|
||||
if newItem is None or newItem.itemHandle is None:
|
||||
return
|
||||
hMap[tHandle] = newItem.itemHandle
|
||||
if newItem.itemParent in hMap:
|
||||
newItem.setParent(hMap[newItem.itemParent])
|
||||
if newItem.isFileType():
|
||||
oldDoc = self._project.storage.getDocument(tHandle)
|
||||
newDoc = self._project.storage.getDocument(newItem.itemHandle)
|
||||
if newDoc.fileExists():
|
||||
return
|
||||
newDoc.writeDocument(oldDoc.readDocument() or "")
|
||||
yield newItem.itemHandle, nHandle
|
||||
nHandle = None
|
||||
|
||||
return
|
||||
|
||||
# END Class DocDuplicator
|
||||
|
||||
|
||||
class ProjectBuilder:
|
||||
"""A class to build a new project from a set of user-defined
|
||||
parameter provided by the New Projecty Wizard.
|
||||
|
||||
@@ -83,6 +83,29 @@ class NWItem:
|
||||
def __bool__(self) -> bool:
|
||||
return self._handle is not None
|
||||
|
||||
def __copy__(self) -> NWItem:
|
||||
"""Make a shallow copy of the current item."""
|
||||
item = NWItem(self._project)
|
||||
item._name = self._name
|
||||
item._handle = self._handle
|
||||
item._parent = self._parent
|
||||
item._root = self._root
|
||||
item._order = self._order
|
||||
item._type = self._type
|
||||
item._class = self._class
|
||||
item._layout = self._layout
|
||||
item._status = self._status
|
||||
item._import = self._import
|
||||
item._active = self._active
|
||||
item._expanded = self._expanded
|
||||
item._heading = self._heading
|
||||
item._charCount = self._charCount
|
||||
item._wordCount = self._wordCount
|
||||
item._paraCount = self._paraCount
|
||||
item._cursorPos = self._cursorPos
|
||||
item._initCount = self._initCount
|
||||
return item
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -163,7 +186,7 @@ class NWItem:
|
||||
# Pack/Unpack Data
|
||||
##
|
||||
|
||||
def pack(self) -> dict[str, dict[str, str]]:
|
||||
def pack(self) -> dict:
|
||||
"""Pack all the data in the class instance into a dictionary."""
|
||||
item: dict[str, str] = {}
|
||||
meta: dict[str, str] = {}
|
||||
@@ -197,7 +220,7 @@ class NWItem:
|
||||
|
||||
return data
|
||||
|
||||
def unpack(self, data: dict[str, dict[str, Any]]) -> bool:
|
||||
def unpack(self, data: dict) -> bool:
|
||||
"""Set the values from a data dictionary."""
|
||||
item = data.get("itemAttr", {})
|
||||
meta = data.get("metaAttr", {})
|
||||
|
||||
@@ -24,10 +24,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import random
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Iterator
|
||||
from typing import TYPE_CHECKING, Iterator
|
||||
from pathlib import Path
|
||||
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||
@@ -114,7 +115,17 @@ class NWTree:
|
||||
|
||||
return True
|
||||
|
||||
def pack(self) -> list[dict[str, dict[str, str]]]:
|
||||
def duplicate(self, sHandle: str) -> NWItem | None:
|
||||
"""Duplicate an item and set a new handle."""
|
||||
sItem = self.__getitem__(sHandle)
|
||||
if isinstance(sItem, NWItem):
|
||||
nItem = copy.copy(sItem)
|
||||
if self.append(None, sItem.itemParent, nItem):
|
||||
logger.info("Duplicated item '%s' -> '%s'", sHandle, nItem.itemHandle)
|
||||
return nItem
|
||||
return None
|
||||
|
||||
def pack(self) -> list[dict]:
|
||||
"""Pack the content of the tree into a list of doctionaries of
|
||||
items. In the order defined by the _treeOrder list.
|
||||
"""
|
||||
@@ -125,7 +136,7 @@ class NWTree:
|
||||
tree.append(tItem.pack())
|
||||
return tree
|
||||
|
||||
def unpack(self, data: list[dict[str, dict[str, Any]]]) -> None:
|
||||
def unpack(self, data: list[dict]) -> None:
|
||||
"""Iterate through all items of a list and add them to the
|
||||
project tree.
|
||||
"""
|
||||
@@ -348,11 +359,11 @@ class NWTree:
|
||||
"""True if there are any items in the project."""
|
||||
return bool(self._treeOrder)
|
||||
|
||||
def __getitem__(self, tHandle: str) -> NWItem | None:
|
||||
def __getitem__(self, tHandle: str | None) -> NWItem | None:
|
||||
"""Return a project item based on its handle. Returns None if
|
||||
the handle doesn't exist in the project.
|
||||
"""
|
||||
if tHandle in self._projTree:
|
||||
if tHandle and tHandle in self._projTree:
|
||||
return self._projTree[tHandle]
|
||||
logger.error("No tree item with handle '%s'", str(tHandle))
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user