Tighten up the item class (#937)

* Protect the attributes of the NWItem class
* Fix test coverage
* Add a type checker function to the tree class
This commit is contained in:
Veronica Berglyd Olsen
2021-12-31 16:25:20 +01:00
committed by GitHub
parent af3d0d2eb0
commit 6ac9e1aec4
14 changed files with 255 additions and 183 deletions
+1 -5
View File
@@ -107,11 +107,7 @@ class NWIndex():
project.
"""
logger.debug("Re-indexing item '%s'", tHandle)
tItem = self.theProject.projTree[tHandle]
if tItem is None:
return False
if tItem.itemType != nwItemType.FILE:
if not self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
return False
theDoc = NWDoc(self.theProject, tHandle)
+135 -65
View File
@@ -42,26 +42,96 @@ class NWItem():
self.theProject = theProject
self.itemName = ""
self.itemHandle = None
self.itemParent = None
self.itemOrder = 0
self.itemType = nwItemType.NO_TYPE
self.itemClass = nwItemClass.NO_CLASS
self.itemLayout = nwItemLayout.NO_LAYOUT
self.itemStatus = None
self.isExpanded = False
self.isExported = True
self._name = ""
self._handle = None
self._parent = None
self._order = 0
self._type = nwItemType.NO_TYPE
self._class = nwItemClass.NO_CLASS
self._layout = nwItemLayout.NO_LAYOUT
self._status = None
self._expanded = False
self._exported = True
# Document Meta Data
self.charCount = 0 # Current character count
self.wordCount = 0 # Current word count
self.paraCount = 0 # Current paragraph count
self.initCount = 0 # Initial word count
self.cursorPos = 0 # Last cursor position
self._charCount = 0 # Current character count
self._wordCount = 0 # Current word count
self._paraCount = 0 # Current paragraph count
self._cursorPos = 0 # Last cursor position
self._initCount = 0 # Initial word count
return
def __repr__(self):
return f"<NWItem handle={self._handle}, parent={self._parent}, name='{self._name}'>"
def __bool__(self):
return self._handle is not None
##
# Properties
##
@property
def itemName(self):
return self._name
@property
def itemHandle(self):
return self._handle
@property
def itemParent(self):
return self._parent
@property
def itemOrder(self):
return self._order
@property
def itemType(self):
return self._type
@property
def itemClass(self):
return self._class
@property
def itemLayout(self):
return self._layout
@property
def itemStatus(self):
return self._status
@property
def isExpanded(self):
return self._expanded
@property
def isExported(self):
return self._exported
@property
def charCount(self):
return self._charCount
@property
def wordCount(self):
return self._wordCount
@property
def paraCount(self):
return self._paraCount
@property
def initCount(self):
return self._initCount
@property
def cursorPos(self):
return self._cursorPos
##
# XML Pack/Unpack
##
@@ -70,23 +140,23 @@ class NWItem():
"""Pack all the data in the class instance into an XML object.
"""
xPack = etree.SubElement(xParent, "item", attrib={
"handle": str(self.itemHandle),
"order": str(self.itemOrder),
"parent": str(self.itemParent),
"handle": str(self._handle),
"order": str(self._order),
"parent": str(self._parent),
})
self._subPack(xPack, "name", text=str(self.itemName))
self._subPack(xPack, "type", text=str(self.itemType.name))
self._subPack(xPack, "class", text=str(self.itemClass.name))
self._subPack(xPack, "status", text=str(self.itemStatus))
if self.itemType == nwItemType.FILE:
self._subPack(xPack, "exported", text=str(self.isExported))
self._subPack(xPack, "layout", text=str(self.itemLayout.name))
self._subPack(xPack, "charCount", text=str(self.charCount), none=False)
self._subPack(xPack, "wordCount", text=str(self.wordCount), none=False)
self._subPack(xPack, "paraCount", text=str(self.paraCount), none=False)
self._subPack(xPack, "cursorPos", text=str(self.cursorPos), none=False)
self._subPack(xPack, "name", text=str(self._name))
self._subPack(xPack, "type", text=str(self._type.name))
self._subPack(xPack, "class", text=str(self._class.name))
self._subPack(xPack, "status", text=str(self._status))
if self._type == nwItemType.FILE:
self._subPack(xPack, "exported", text=str(self._exported))
self._subPack(xPack, "layout", text=str(self._layout.name))
self._subPack(xPack, "charCount", text=str(self._charCount), none=False)
self._subPack(xPack, "wordCount", text=str(self._wordCount), none=False)
self._subPack(xPack, "paraCount", text=str(self._paraCount), none=False)
self._subPack(xPack, "cursorPos", text=str(self._cursorPos), none=False)
else:
self._subPack(xPack, "expanded", text=str(self.isExpanded))
self._subPack(xPack, "expanded", text=str(self._expanded))
return
@@ -162,12 +232,12 @@ class NWItem():
"""Return a string description of the item.
"""
descKey = "none"
if self.itemType == nwItemType.ROOT:
if self._type == nwItemType.ROOT:
descKey = "root"
elif self.itemType == nwItemType.FOLDER:
elif self._type == nwItemType.FOLDER:
descKey = "folder"
elif self.itemType == nwItemType.FILE:
if self.itemLayout == nwItemLayout.DOCUMENT:
elif self._type == nwItemType.FILE:
if self._layout == nwItemLayout.DOCUMENT:
if hLevel == "H1":
descKey = "doc_h1"
elif hLevel == "H2":
@@ -176,7 +246,7 @@ class NWItem():
descKey = "doc_h3"
else:
descKey = "document"
elif self.itemLayout == nwItemLayout.NOTE:
elif self._layout == nwItemLayout.NOTE:
descKey = "note"
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
@@ -189,29 +259,29 @@ class NWItem():
"""Set the item name.
"""
if isinstance(theName, str):
self.itemName = theName.strip()
self._name = theName.strip()
else:
self.itemName = ""
self._name = ""
return
def setHandle(self, theHandle):
"""Set the item handle, and ensure it is valid.
"""
if isHandle(theHandle):
self.itemHandle = theHandle
self._handle = theHandle
else:
self.itemHandle = None
self._handle = None
return
def setParent(self, theParent):
"""Set the parent handle, and ensure it is valid.
"""
if theParent is None:
self.itemParent = None
self._parent = None
elif isHandle(theParent):
self.itemParent = theParent
self._parent = theParent
else:
self.itemParent = None
self._parent = None
return
def setOrder(self, theOrder):
@@ -219,7 +289,7 @@ class NWItem():
is purely a meta value, and not actually used by novelWriter at
the moment.
"""
self.itemOrder = checkInt(theOrder, 0)
self._order = checkInt(theOrder, 0)
return
def setType(self, theType):
@@ -227,12 +297,12 @@ class NWItem():
from a string representing an nwItemType.
"""
if isinstance(theType, nwItemType):
self.itemType = theType
self._type = theType
elif isItemType(theType):
self.itemType = nwItemType[theType]
self._type = nwItemType[theType]
else:
logger.error("Unrecognised item type '%s'", theType)
self.itemType = nwItemType.NO_TYPE
self._type = nwItemType.NO_TYPE
return
def setClass(self, theClass):
@@ -240,12 +310,12 @@ class NWItem():
it from a string representing an nwItemClass.
"""
if isinstance(theClass, nwItemClass):
self.itemClass = theClass
self._class = theClass
elif isItemClass(theClass):
self.itemClass = nwItemClass[theClass]
self._class = nwItemClass[theClass]
else:
logger.error("Unrecognised item class '%s'", theClass)
self.itemClass = nwItemClass.NO_CLASS
self._class = nwItemClass.NO_CLASS
return
def setLayout(self, theLayout):
@@ -253,42 +323,42 @@ class NWItem():
it from a string representing an nwItemLayout.
"""
if isinstance(theLayout, nwItemLayout):
self.itemLayout = theLayout
self._layout = theLayout
elif isItemLayout(theLayout):
self.itemLayout = nwItemLayout[theLayout]
self._layout = nwItemLayout[theLayout]
elif theLayout in nwLists.DEP_LAYOUT:
self.itemLayout = nwItemLayout.DOCUMENT
self._layout = nwItemLayout.DOCUMENT
else:
logger.error("Unrecognised item layout '%s'", theLayout)
self.itemLayout = nwItemLayout.NO_LAYOUT
self._layout = nwItemLayout.NO_LAYOUT
return
def setStatus(self, theStatus):
"""Set the item status by looking it up in the valid status
items of the current project.
"""
if self.itemClass in nwLists.CLS_NOVEL:
self.itemStatus = self.theProject.statusItems.checkEntry(theStatus)
if self._class in nwLists.CLS_NOVEL:
self._status = self.theProject.statusItems.checkEntry(theStatus)
else:
self.itemStatus = self.theProject.importItems.checkEntry(theStatus)
self._status = self.theProject.importItems.checkEntry(theStatus)
return
def setExpanded(self, expState):
"""Set the expanded status of an item in the project tree.
"""
if isinstance(expState, str):
self.isExpanded = (expState == str(True))
self._expanded = (expState == str(True))
else:
self.isExpanded = (expState is True)
self._expanded = (expState is True)
return
def setExported(self, expState):
"""Set the export flag.
"""
if isinstance(expState, str):
self.isExported = (expState == str(True))
self._exported = (expState == str(True))
else:
self.isExported = (expState is True)
self._exported = (expState is True)
return
##
@@ -298,31 +368,31 @@ class NWItem():
def setCharCount(self, theCount):
"""Set the character count, and ensure that it is an integer.
"""
self.charCount = max(0, checkInt(theCount, 0))
self._charCount = max(0, checkInt(theCount, 0))
return
def setWordCount(self, theCount):
"""Set the word count, and ensure that it is an integer.
"""
self.wordCount = max(0, checkInt(theCount, 0))
self._wordCount = max(0, checkInt(theCount, 0))
return
def setParaCount(self, theCount):
"""Set the paragraph count, and ensure that it is an integer.
"""
self.paraCount = max(0, checkInt(theCount, 0))
self._paraCount = max(0, checkInt(theCount, 0))
return
def setCursorPos(self, thePosition):
"""Set the cursor position, and ensure that it is an integer.
"""
self.cursorPos = max(0, checkInt(thePosition, 0))
self._cursorPos = max(0, checkInt(thePosition, 0))
return
def saveInitialCount(self):
"""Save the initial word count.
"""
self.initCount = self.wordCount
self._initCount = self._wordCount
return
# END Class NWItem
+1 -1
View File
@@ -1166,7 +1166,7 @@ class NWProject():
nMax = min(len(iterItems), 10000)
while n < nMax:
tHandle = iterItems[n]
tItem = self.projTree[tHandle]
tItem = self.projTree[tHandle]
n += 1
if tItem is None:
# Technically a bug since treeOrder is built from the
+3 -6
View File
@@ -270,11 +270,7 @@ class Tokenizer():
def addRootHeading(self, theHandle):
"""Add a heading at the start of a new root folder.
"""
theItem = self.theProject.projTree[theHandle]
if theItem is None:
return False
if theItem.itemType != nwItemType.ROOT:
if not self.theProject.projTree.checkType(theHandle, nwItemType.ROOT):
return False
if self._isFirst:
@@ -283,6 +279,7 @@ class Tokenizer():
else:
textAlign = self.A_PBB | self.A_CENTRE
theItem = self.theProject.projTree[theHandle]
locNotes = self._localLookup("Notes")
theTitle = f"{locNotes}: {theItem.itemName}"
self._theTokens = []
@@ -299,7 +296,7 @@ class Tokenizer():
not set, load it from the file.
"""
self._theHandle = theHandle
self._theItem = self.theProject.projTree[theHandle]
self._theItem = self.theProject.projTree[theHandle]
if self._theItem is None:
return False
+8
View File
@@ -210,6 +210,14 @@ class NWTree():
# Tree Structure Methods
##
def checkType(self, tHandle, itemType):
"""Return true of item exists and is of the specified item type.
"""
tItem = self.__getitem__(tHandle)
if not tItem:
return False
return tItem.itemType == itemType
def trashRoot(self):
"""Returns the handle of the trash folder, or None if there
isn't one.