Rewrite NWItem to not allow handle to be None

This commit is contained in:
Veronica Berglyd Olsen
2023-07-21 22:52:54 +02:00
parent 4dfa51855e
commit 9a1a2d9ab2
7 changed files with 125 additions and 120 deletions
-2
View File
@@ -353,8 +353,6 @@ class BuildSettings:
for item in project.tree:
tHandle = item.itemHandle
if tHandle is None:
continue
if item.isInactiveClass() or (item.itemRoot in self._skipRoot):
result[tHandle] = (False, FilterMode.SKIPPED)
continue
+1 -1
View File
@@ -287,7 +287,7 @@ class DocDuplicator:
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:
if newItem is None:
return
hMap[tHandle] = newItem.itemHandle
if newItem.itemParent in hMap:
-2
View File
@@ -101,8 +101,6 @@ class NWBuildDocument:
self._queue = []
filtered = self._build.buildItemFilter(self._project)
for item in self._project.tree:
if not item.itemHandle:
continue
if filtered.get(item.itemHandle, False):
self._queue.append(item.itemHandle)
return
+1 -1
View File
@@ -243,7 +243,7 @@ class NWDocument:
"""Return a pointer to the currently open NWItem."""
return self._theItem
def getMeta(self) -> tuple[str, str | None, str | None, str | None]:
def getMeta(self) -> tuple[str, str | None, nwItemClass | None, nwItemLayout | None]:
"""Parse the document meta tag and return the name, parent,
class and layout meta values.
"""
+43 -40
View File
@@ -1,7 +1,6 @@
"""
novelWriter Project Item Class
================================
Data class for a project tree item
File History:
Created: 2018-10-27 [0.0.1]
@@ -43,6 +42,13 @@ logger = logging.getLogger(__name__)
class NWItem:
"""Core: Item Data Class
This class holds all the project information about a project item.
Each item must be associated with a project and have a valid handle.
Only the NWTree class should create instances of this class, and
must ensure that the handle is valid for all items in the tree.
"""
__slots__ = (
"_project", "_name", "_handle", "_parent", "_root", "_order",
@@ -51,11 +57,11 @@ class NWItem:
"_paraCount", "_cursorPos", "_initCount",
)
def __init__(self, project: NWProject) -> None:
def __init__(self, project: NWProject, handle: str) -> None:
self._project = project
self._name = ""
self._handle = None
self._handle = handle
self._parent = None
self._root = None
self._order = 0
@@ -81,31 +87,12 @@ class NWItem:
return f"<NWItem handle={self._handle}, parent={self._parent}, name='{self._name}'>"
def __bool__(self) -> bool:
"""Evaluate to False if itemHandle is not set."""
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
"""The truthiness of the class. The handle used to be initiated
to None, but this is no longer the case. It should always
evaluate to True since 2.1-beta1, although unpack and the NWTree
class can leave it as an empty string.
"""
return bool(self._handle)
##
# Properties
@@ -116,7 +103,7 @@ class NWItem:
return self._name
@property
def itemHandle(self) -> str | None:
def itemHandle(self) -> str:
return self._handle
@property
@@ -184,7 +171,7 @@ class NWItem:
return self._cursorPos
##
# Pack/Unpack Data
# Pack/Unpack/Duplicate Data
##
def pack(self) -> dict:
@@ -227,8 +214,9 @@ class NWItem:
meta = data.get("metaAttr", {})
name = data.get("nameAttr", {})
if "handle" in item:
self.setHandle(item["handle"])
handle = item.get("handle", "")
if isHandle(handle):
self._handle = handle
else:
logger.error("Item does not have a handle")
return False
@@ -269,6 +257,29 @@ class NWItem:
return True
@classmethod
def duplicate(cls, source: NWItem, handle: str) -> NWItem:
"""Make a copy of an item."""
cls = NWItem(source._project, handle)
cls._name = source._name
cls._parent = source._parent
cls._root = source._root
cls._order = source._order
cls._type = source._type
cls._class = source._class
cls._layout = source._layout
cls._status = source._status
cls._import = source._import
cls._active = source._active
cls._expanded = source._expanded
cls._heading = source._heading
cls._charCount = source._charCount
cls._wordCount = source._wordCount
cls._paraCount = source._paraCount
cls._cursorPos = source._cursorPos
cls._initCount = source._initCount
return cls
##
# Lookup Methods
##
@@ -387,14 +398,6 @@ class NWItem:
self._name = ""
return
def setHandle(self, handle: Any) -> None:
"""Set the item handle, and ensure it is valid."""
if isHandle(handle):
self._handle = handle
else:
self._handle = None
return
def setParent(self, handle: Any) -> None:
"""Set the parent handle, and ensure it is valid."""
if handle is None:
+19 -56
View File
@@ -38,7 +38,6 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException
from novelwriter.constants import trConst, nwLabels
from novelwriter.core.tree import NWTree
from novelwriter.core.item import NWItem
from novelwriter.core.index import NWIndex
from novelwriter.core.options import OptionState
from novelwriter.core.storage import NWStorage
@@ -130,42 +129,20 @@ class NWProject(QObject):
# Item Methods
##
def newRoot(self, itemClass, label=None):
"""Add a new root item. If label is None, use the class label.
def newRoot(self, itemClass: nwItemClass, label: str | None = None) -> str:
"""Add a new root folder to the project. If label is not set,
use the class label.
"""
if label is None:
label = trConst(nwLabels.CLASS_NAME[itemClass])
newItem = NWItem(self)
newItem.setName(label)
newItem.setType(nwItemType.ROOT)
newItem.setClass(itemClass)
self._tree.append(None, None, newItem)
self._tree.updateItemData(newItem.itemHandle)
return newItem.itemHandle
label = label or trConst(nwLabels.CLASS_NAME[itemClass])
return self._tree.create(label, None, nwItemType.ROOT, itemClass)
def newFolder(self, label, pHandle):
"""Add a new folder with a given label and parent item.
"""
if pHandle not in self._tree:
return None
newItem = NWItem(self)
newItem.setName(label)
newItem.setType(nwItemType.FOLDER)
self._tree.append(None, pHandle, newItem)
self._tree.updateItemData(newItem.itemHandle)
return newItem.itemHandle
def newFolder(self, label: str, parent: str) -> str | None:
"""Add a new folder with a given label and parent item."""
return self._tree.create(label, parent, nwItemType.FOLDER)
def newFile(self, label, pHandle):
"""Add a new file with a given label and parent item.
"""
if pHandle not in self._tree:
return None
newItem = NWItem(self)
newItem.setName(label)
newItem.setType(nwItemType.FILE)
self._tree.append(None, pHandle, newItem)
self._tree.updateItemData(newItem.itemHandle)
return newItem.itemHandle
def newFile(self, label: str, parent: str) -> str | None:
"""Add a new file with a given label and parent item."""
return self._tree.create(label, parent, nwItemType.FILE)
def writeNewFile(self, tHandle, hLevel, isDocument, addText=""):
"""Write content to a new document after it is created. This
@@ -211,18 +188,11 @@ class NWProject(QObject):
return True
def trashFolder(self):
"""Add the special trash root folder to the project.
"""
"""Add the special trash root folder to the project."""
trashHandle = self._tree.trashRoot()
if trashHandle is None:
newItem = NWItem(self)
newItem.setName(trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH]))
newItem.setType(nwItemType.ROOT)
newItem.setClass(nwItemClass.TRASH)
self._tree.append(None, None, newItem)
self._tree.updateItemData(newItem.itemHandle)
return newItem.itemHandle
label = trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH])
return self._tree.create(label, None, nwItemType.ROOT, nwItemClass.TRASH)
return trashHandle
##
@@ -769,11 +739,8 @@ class NWProject(QObject):
oName = self.tr("Recovered File {0}").format(nOrph)
# Recover file meta data
if oClass is None:
oClass = nwItemClass.NOVEL
if oLayout is None:
oLayout = nwItemLayout.NOTE
oClass = oClass or nwItemClass.NOVEL
oLayout = oLayout or nwItemLayout.NOTE
if oParent is None or oParent not in self._tree:
oParent = self._tree.findRoot(oClass)
@@ -785,13 +752,9 @@ class NWProject(QObject):
noWhere = True
continue
orphItem = NWItem(self)
orphItem.setName(oName)
orphItem.setType(nwItemType.FILE)
orphItem.setClass(oClass)
orphItem.setLayout(oLayout)
self._tree.append(oHandle, oParent, orphItem)
self._tree.updateItemData(orphItem.itemHandle)
nHandle = self._tree.create(oName, oParent, nwItemType.FILE, oClass, oLayout)
if nHandle is not None:
(contentPath / f"{oHandle}.nwd").rename(contentPath / f"{nHandle}.nwd")
if noWhere:
self.mainGui.makeAlert(self.tr(
+61 -18
View File
@@ -1,7 +1,6 @@
"""
novelWriter Project Tree Class
================================
Data class for the project's tree of project items
File History:
Created: 2020-05-07 [0.4.5]
@@ -24,16 +23,15 @@ 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, Iterator
from typing import TYPE_CHECKING, Iterator, overload
from pathlib import Path
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
from novelwriter.common import checkHandle
from novelwriter.common import isHandle
from novelwriter.constants import nwFiles
from novelwriter.core.item import NWItem
@@ -44,6 +42,22 @@ logger = logging.getLogger(__name__)
class NWTree:
"""Core: Project Tree Data Class
Only one instance of this class should exist in the project class.
This class holds all the project items of the project as instances
of NWItem.
For historical reasons, the order of the items is saved in a
separate list from the items themselves, which are stored in a
dictionary. This is somewhat redundant with the newer versions of
Python, but is still practical as it's easier to update the item
order as a list.
Each item has a handle, which is a random hex string of length 13.
The handle is the name of the item everywhere in novelWriter, and is
also used for file names.
"""
MAX_DEPTH = 1000 # Cap of tree traversing for loops
@@ -52,7 +66,7 @@ class NWTree:
self._project = project
self._projTree: dict[str, NWItem] = {} # Holds all the items of the project
self._treeOrder: list[str] = [] # The order of the tree items on the tree view
self._treeOrder: list[str] = [] # The order of the tree items in the tree view
self._treeRoots: dict[str, NWItem] = {} # The root items of the tree
self._trashRoot = None # The handle of the trash root folder
@@ -79,12 +93,44 @@ class NWTree:
"""Returns a copy of the list of all the active handles."""
return self._treeOrder.copy()
def append(self, tHandle: str | None, pHandle: str | None, nwItem: NWItem) -> bool:
"""Add a new item to the end of the tree."""
tHandle = checkHandle(tHandle, None, True)
pHandle = checkHandle(pHandle, None, True)
if tHandle is None:
@overload
def create(self, label: str, parent: None, itemType: nwItemType,
itemClass: nwItemClass = nwItemClass.NO_CLASS,
itemLayout: nwItemLayout = nwItemLayout.NO_LAYOUT) -> str:
...
@overload
def create(self, label: str, parent: str | None, itemType: nwItemType,
itemClass: nwItemClass = nwItemClass.NO_CLASS,
itemLayout: nwItemLayout = nwItemLayout.NO_LAYOUT) -> str | None:
...
def create(self, label, parent, itemType,
itemClass=nwItemClass.NO_CLASS, itemLayout=nwItemLayout.NO_LAYOUT):
"""Create a new item in the project tree, and return its handle.
If the item cannot be added to the project, None is returned.
"""
if parent is None or parent in self._treeOrder:
tHandle = self._makeHandle()
newItem = NWItem(self._project, tHandle)
newItem.setName(label)
newItem.setParent(parent)
newItem.setType(itemType)
newItem.setClass(itemClass)
newItem.setLayout(itemLayout)
self.append(newItem)
self.updateItemData(tHandle)
return tHandle
return None
def append(self, nwItem: NWItem) -> bool:
"""Add a new item to the end of the tree."""
tHandle = nwItem.itemHandle
pHandle = nwItem.itemParent
if not isHandle(tHandle):
logger.warning("Invalid item handle '%s' detected, skipping", tHandle)
return False
if tHandle in self._projTree:
logger.warning("Duplicate handle '%s' detected, skipping", tHandle)
@@ -92,9 +138,6 @@ class NWTree:
logger.debug("Adding item '%s' with parent '%s'", str(tHandle), str(pHandle))
nwItem.setHandle(tHandle)
nwItem.setParent(pHandle)
if nwItem.isRootType():
logger.debug("Item '%s' is a root item", str(tHandle))
self._treeRoots[tHandle] = nwItem
@@ -119,8 +162,8 @@ class NWTree:
"""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):
nItem = NWItem.duplicate(sItem, self._makeHandle())
if self.append(nItem):
logger.info("Duplicated item '%s' -> '%s'", sHandle, nItem.itemHandle)
return nItem
return None
@@ -142,9 +185,9 @@ class NWTree:
"""
self.clear()
for item in data:
nwItem = NWItem(self._project)
nwItem = NWItem(self._project, "NOTSET") # Handle is set by unpack()
if nwItem.unpack(item):
self.append(nwItem.itemHandle, nwItem.itemParent, nwItem)
self.append(nwItem)
nwItem.saveInitialCount()
return
@@ -314,7 +357,7 @@ class NWTree:
return self._trashRoot
return None
def findRoot(self, itemClass: nwItemClass) -> str | None:
def findRoot(self, itemClass: nwItemClass | None) -> str | None:
"""Find the first root item for a given class."""
for aRoot in self._treeRoots:
tItem = self.__getitem__(aRoot)