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.item import NWItem
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -76,7 +76,7 @@ class DocMerger:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def newTargetDoc(self, srcHandle: str, docLabel: str) -> str | None:
|
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.
|
and a new doc label. Calling this function resets the class.
|
||||||
"""
|
"""
|
||||||
srcItem = self._project.tree[srcHandle]
|
srcItem = self._project.tree[srcHandle]
|
||||||
@@ -263,6 +263,49 @@ class DocSplitter:
|
|||||||
# END 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:
|
class ProjectBuilder:
|
||||||
"""A class to build a new project from a set of user-defined
|
"""A class to build a new project from a set of user-defined
|
||||||
parameter provided by the New Projecty Wizard.
|
parameter provided by the New Projecty Wizard.
|
||||||
|
|||||||
@@ -83,6 +83,29 @@ class NWItem:
|
|||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
return self._handle is not None
|
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
|
# Properties
|
||||||
##
|
##
|
||||||
@@ -163,7 +186,7 @@ class NWItem:
|
|||||||
# Pack/Unpack Data
|
# 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."""
|
"""Pack all the data in the class instance into a dictionary."""
|
||||||
item: dict[str, str] = {}
|
item: dict[str, str] = {}
|
||||||
meta: dict[str, str] = {}
|
meta: dict[str, str] = {}
|
||||||
@@ -197,7 +220,7 @@ class NWItem:
|
|||||||
|
|
||||||
return data
|
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."""
|
"""Set the values from a data dictionary."""
|
||||||
item = data.get("itemAttr", {})
|
item = data.get("itemAttr", {})
|
||||||
meta = data.get("metaAttr", {})
|
meta = data.get("metaAttr", {})
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
import random
|
import random
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, Iterator
|
from typing import TYPE_CHECKING, Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||||
@@ -114,7 +115,17 @@ class NWTree:
|
|||||||
|
|
||||||
return True
|
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
|
"""Pack the content of the tree into a list of doctionaries of
|
||||||
items. In the order defined by the _treeOrder list.
|
items. In the order defined by the _treeOrder list.
|
||||||
"""
|
"""
|
||||||
@@ -125,7 +136,7 @@ class NWTree:
|
|||||||
tree.append(tItem.pack())
|
tree.append(tItem.pack())
|
||||||
return tree
|
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
|
"""Iterate through all items of a list and add them to the
|
||||||
project tree.
|
project tree.
|
||||||
"""
|
"""
|
||||||
@@ -348,11 +359,11 @@ class NWTree:
|
|||||||
"""True if there are any items in the project."""
|
"""True if there are any items in the project."""
|
||||||
return bool(self._treeOrder)
|
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
|
"""Return a project item based on its handle. Returns None if
|
||||||
the handle doesn't exist in the project.
|
the handle doesn't exist in the project.
|
||||||
"""
|
"""
|
||||||
if tHandle in self._projTree:
|
if tHandle and tHandle in self._projTree:
|
||||||
return self._projTree[tHandle]
|
return self._projTree[tHandle]
|
||||||
logger.error("No tree item with handle '%s'", str(tHandle))
|
logger.error("No tree item with handle '%s'", str(tHandle))
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user