Merge branch 'dev' into item_layouts

This commit is contained in:
Veronica K. B. Olsen
2021-01-29 20:20:16 +01:00
15 changed files with 879 additions and 132 deletions
+3 -3
View File
@@ -32,7 +32,7 @@ import logging
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QErrorMessage from PyQt5.QtWidgets import QApplication, QErrorMessage
from nw.error import exceptionHandler from nw.error import exceptionHandler, logException
from nw.config import Config from nw.config import Config
## ##
@@ -268,9 +268,9 @@ def main(sysArgs=None):
bundle = NSBundle.mainBundle() bundle = NSBundle.mainBundle()
info = bundle.localizedInfoDictionary() or bundle.infoDictionary() info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
info["CFBundleName"] = "novelWriter" info["CFBundleName"] = "novelWriter"
except ImportError as e: except ImportError:
logger.error("Failed to set application name") logger.error("Failed to set application name")
logger.error(str(e)) logException()
# Import GUI (after dependency checks), and launch # Import GUI (after dependency checks), and launch
from nw.guimain import GuiMain from nw.guimain import GuiMain
+34 -4
View File
@@ -30,7 +30,9 @@ from datetime import datetime
from PyQt5.QtWidgets import qApp from PyQt5.QtWidgets import qApp
from nw.constants import nwConst, nwUnicode from nw.constants import (
nwConst, nwUnicode, nwItemClass, nwItemType, nwItemLayout
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -103,11 +105,39 @@ def isHandle(theString):
return False return False
if len(theString) != 13: if len(theString) != 13:
return False return False
invalidChar = False
for c in theString: for c in theString:
if c not in "0123456789abcdef": if c not in "0123456789abcdef":
invalidChar = True return False
return not invalidChar return True
def isTitleTag(theString):
"""Check if a string is a valid title string.
"""
if not isinstance(theString, str):
return False
if len(theString) != 7:
return False
if not theString.startswith("T"):
return False
for c in theString[1:]:
if c not in "0123456789":
return False
return True
def isItemClass(theString):
"""Check if an item is a calid nwItemClass identifier.
"""
return theString in nwItemClass.__members__
def isItemType(theString):
"""Check if an item is a calid nwItemType identifier.
"""
return theString in nwItemType.__members__
def isItemLayout(theString):
"""Check if an item is a calid nwItemLayout identifier.
"""
return theString in nwItemLayout.__members__
def hexToInt(value, default=0): def hexToInt(value, default=0):
"""Convert a hex string to an integer. """Convert a hex string to an integer.
+7 -6
View File
@@ -38,6 +38,7 @@ from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
from nw.constants import nwConst, nwFiles, nwUnicode from nw.constants import nwConst, nwFiles, nwUnicode
from nw.common import splitVersionNumber, formatTimeStamp from nw.common import splitVersionNumber, formatTimeStamp
from nw.error import logException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -293,7 +294,7 @@ class Config:
os.mkdir(self.confPath) os.mkdir(self.confPath)
except Exception as e: except Exception as e:
logger.error("Could not create folder: %s" % self.confPath) logger.error("Could not create folder: %s" % self.confPath)
logger.error(str(e)) logException()
self.hasError = True self.hasError = True
self.errData.append("Could not create folder: %s" % self.confPath) self.errData.append("Could not create folder: %s" % self.confPath)
self.errData.append(str(e)) self.errData.append(str(e))
@@ -316,7 +317,7 @@ class Config:
os.mkdir(self.dataPath) os.mkdir(self.dataPath)
except Exception as e: except Exception as e:
logger.error("Could not create folder: %s" % self.dataPath) logger.error("Could not create folder: %s" % self.dataPath)
logger.error(str(e)) logException()
self.hasError = True self.hasError = True
self.errData.append("Could not create folder: %s" % self.dataPath) self.errData.append("Could not create folder: %s" % self.dataPath)
self.errData.append(str(e)) self.errData.append(str(e))
@@ -361,7 +362,7 @@ class Config:
cnfParse.read_file(inFile) cnfParse.read_file(inFile)
except Exception as e: except Exception as e:
logger.error("Could not load config file") logger.error("Could not load config file")
logger.error(str(e)) logException()
self.hasError = True self.hasError = True
self.errData.append("Could not load config file") self.errData.append("Could not load config file")
self.errData.append(str(e)) self.errData.append(str(e))
@@ -702,7 +703,7 @@ class Config:
self.confChanged = False self.confChanged = False
except Exception as e: except Exception as e:
logger.error("Could not save config file") logger.error("Could not save config file")
logger.error(str(e)) logException()
self.hasError = True self.hasError = True
self.errData.append("Could not save config file") self.errData.append("Could not save config file")
self.errData.append(str(e)) self.errData.append(str(e))
@@ -978,9 +979,9 @@ class Config:
return self._unpackList( return self._unpackList(
cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_S_LST cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_S_LST
) )
except ValueError as e: except ValueError:
logger.error("Failed to load value from config file.") logger.error("Failed to load value from config file.")
logger.error(str(e)) logException()
return cnfDefault return cnfDefault
return cnfDefault return cnfDefault
+162 -30
View File
@@ -36,11 +36,13 @@ from nw.constants import (
) )
from nw.core.document import NWDoc from nw.core.document import NWDoc
from nw.core.tools import countWords from nw.core.tools import countWords
from nw.common import isHandle, isTitleTag, isItemClass, isItemLayout
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NWIndex(): class NWIndex():
H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4} H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
@@ -155,9 +157,14 @@ class NWIndex():
try: try:
with open(indexFile, mode="r", encoding="utf8") as inFile: with open(indexFile, mode="r", encoding="utf8") as inFile:
theData = json.load(inFile) theData = json.load(inFile)
except Exception as e: except Exception:
logger.error("Failed to load index file") logger.error("Failed to load index file")
logger.error(str(e)) nw.logException()
self.indexBroken = True
self.theParent.makeAlert(
"Could not load cached index file. Rebuilding index.",
nwAlert.WARN
)
return False return False
self._tagIndex = theData.get("tagIndex", {}) self._tagIndex = theData.get("tagIndex", {})
@@ -193,9 +200,9 @@ class NWIndex():
"textCounts" : self._textCounts, "textCounts" : self._textCounts,
"firstTitle" : self._firstTitle, "firstTitle" : self._firstTitle,
}, outFile, indent=2) }, outFile, indent=2)
except Exception as e: except Exception:
logger.error("Failed to save index file") logger.error("Failed to save index file")
logger.error(str(e)) nw.logException()
return False return False
return True return True
@@ -205,43 +212,29 @@ class NWIndex():
elements it should. elements it should.
""" """
logger.debug("Checking index") logger.debug("Checking index")
self.indexBroken = False tStart = time()
try: try:
for tTag in self._tagIndex: self._checkTagIndex()
if len(self._tagIndex[tTag]) != 4: self._checkRefIndex()
self.indexBroken = True self._checkNovelNoteIndex("novelIndex")
self._checkNovelNoteIndex("noteIndex")
for tHandle in self._refIndex: self._checkTextCounts()
for sTitle in self._refIndex[tHandle]: self.indexBroken = False
for tEntry in self._refIndex[tHandle][sTitle]["tags"]:
if len(tEntry) != 3:
self.indexBroken = True
for tHandle in self._novelIndex:
for sLine in self._novelIndex[tHandle]:
if len(self._novelIndex[tHandle][sLine].keys()) != 8:
self.indexBroken = True
for tHandle in self._noteIndex:
for sLine in self._noteIndex[tHandle]:
if len(self._noteIndex[tHandle][sLine].keys()) != 8:
self.indexBroken = True
for tHandle in self._textCounts:
if len(self._textCounts[tHandle]) != 3:
self.indexBroken = True
for tHandle in self._firstTitle: for tHandle in self._firstTitle:
if len(self._firstTitle[tHandle]) != 2: if len(self._firstTitle[tHandle]) != 2:
self.indexBroken = True self.indexBroken = True
except Exception as e: except Exception:
logger.error("Error while checking index") logger.error("Error while checking index")
logger.error(str(e)) nw.logException()
self.indexBroken = True self.indexBroken = True
tEnd = time()
logger.debug("Index check took %.3f ms" % ((tEnd - tStart)*1000))
logger.debug("Index check complete") logger.debug("Index check complete")
if self.indexBroken: if self.indexBroken:
self.clearIndex() self.clearIndex()
self.theParent.makeAlert( self.theParent.makeAlert(
@@ -765,4 +758,143 @@ class NWIndex():
return theHandles return theHandles
##
# Index Checkers
##
def _checkTagIndex(self):
"""Scan the tag index for errors.
Waring: This function raises exceptions.
"""
for tTag in self._tagIndex:
if not isinstance(tTag, str):
raise KeyError("tagIndex key is not a string")
tEntry = self._tagIndex[tTag]
if len(tEntry) != 4:
raise IndexError("tagIndex[a] expected 4 values")
if not isinstance(tEntry[0], int):
raise ValueError("tagIndex[a][0] is not an integer")
if not isHandle(tEntry[1]):
raise ValueError("tagIndex[a][1] is not a handle")
if not isItemClass(tEntry[2]):
raise ValueError("tagIndex[a][2] is not an nwItemClass")
if not isTitleTag(tEntry[3]):
raise ValueError("tagIndex[a][3] is not a title tag")
return
def _checkRefIndex(self):
"""Scan the reference index for errors.
Waring: This function raises exceptions.
"""
for tHandle in self._refIndex:
if not isHandle(tHandle):
raise KeyError("refIndex key is not a handle")
hEntry = self._refIndex[tHandle]
for sTitle in hEntry:
if not isTitleTag(sTitle):
raise KeyError("refIndex[a] key is not a title tag")
sEntry = hEntry[sTitle]
if "tags" not in sEntry:
raise KeyError("refIndex[a][b] has no 'tag' key")
for tEntry in sEntry["tags"]:
if len(tEntry) != 3:
raise IndexError("refIndex[a][b][tags][i] expected 3 values")
if not isinstance(tEntry[0], int):
raise ValueError("refIndex[a][b][tags][i][0] is not an integer")
if not tEntry[1] in nwKeyWords.VALID_KEYS:
raise ValueError("refIndex[a][b][tags][i][1] is not a keyword")
if not isinstance(tEntry[2], str):
raise ValueError("refIndex[a][b][tags][i][2] is not a string")
if "updated" not in sEntry:
raise KeyError("refIndex[a][b] has no 'updated' key")
if not isinstance(sEntry["updated"], int):
raise ValueError("%refIndex[a][b][updated] is not an integer")
return
def _checkNovelNoteIndex(self, idxName):
"""Scan the novel or note index for errors.
Waring: This function raises exceptions.
"""
if idxName == "novelIndex":
theIndex = self._novelIndex
elif idxName == "noteIndex":
theIndex = self._noteIndex
else:
raise IndexError("Unknown index %s" % idxName)
for tHandle in theIndex:
if not isHandle(tHandle):
raise KeyError("%s key is not a handle" % idxName)
hEntry = theIndex[tHandle]
for sTitle in theIndex[tHandle]:
if not isTitleTag(sTitle):
raise KeyError("%s[a] key is not a title tag" % idxName)
sEntry = hEntry[sTitle]
if len(sEntry) != 8:
raise IndexError("%s[a][b] expected 8 values" % idxName)
if "level" not in sEntry:
raise KeyError("%s[a][b] has no 'level' key" % idxName)
if "title" not in sEntry:
raise KeyError("%s[a][b] has no 'title' key" % idxName)
if "layout" not in sEntry:
raise KeyError("%s[a][b] has no 'layout' key" % idxName)
if "synopsis" not in sEntry:
raise KeyError("%s[a][b] has no 'synopsis' key" % idxName)
if "cCount" not in sEntry:
raise KeyError("%s[a][b] has no 'cCount' key" % idxName)
if "wCount" not in sEntry:
raise KeyError("%s[a][b] has no 'wCount' key" % idxName)
if "pCount" not in sEntry:
raise KeyError("%s[a][b] has no 'pCount' key" % idxName)
if "updated" not in sEntry:
raise KeyError("%s[a][b] has no 'updated' key" % idxName)
if not sEntry["level"] in self.H_VALID:
raise ValueError("%s[a][b][level] is not a header level" % idxName)
if not isinstance(sEntry["title"], str):
raise ValueError("%s[a][b][title] is not a string" % idxName)
if not isItemLayout(sEntry["layout"]):
raise ValueError("%s[a][b][layout] is not an nwItemLayout" % idxName)
if not isinstance(sEntry["synopsis"], str):
raise ValueError("%s[a][b][synopsis] is not a string" % idxName)
if not isinstance(sEntry["cCount"], int):
raise ValueError("%s[a][b][cCount] is not an integer" % idxName)
if not isinstance(sEntry["wCount"], int):
raise ValueError("%s[a][b][wCount] is not an integer" % idxName)
if not isinstance(sEntry["pCount"], int):
raise ValueError("%s[a][b][pCount] is not an integer" % idxName)
if not isinstance(sEntry["updated"], int):
raise ValueError("%s[a][b][updated] is not an integer" % idxName)
return
def _checkTextCounts(self):
"""Scan the text counts index for errors.
Waring: This function raises exceptions.
"""
for tHandle in self._textCounts:
if not isHandle(tHandle):
raise KeyError("textCounts key is not a handle")
tEntry = self._textCounts[tHandle]
if len(tEntry) != 3:
raise IndexError("textCounts[a] expected 3 values")
if not isinstance(tEntry[0], int):
raise ValueError("textCounts[a][0] is not an integer")
if not isinstance(tEntry[1], int):
raise ValueError("textCounts[a][1] is not an integer")
if not isinstance(tEntry[2], int):
raise ValueError("textCounts[a][2] is not an integer")
return
# END Class NWIndex # END Class NWIndex
+6 -4
View File
@@ -28,8 +28,10 @@ import logging
from lxml import etree from lxml import etree
from nw.common import checkInt, isHandle
from nw.constants import nwItemType, nwItemClass, nwItemLayout from nw.constants import nwItemType, nwItemClass, nwItemLayout
from nw.common import (
checkInt, isHandle, isItemClass, isItemLayout, isItemType
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -201,7 +203,7 @@ class NWItem():
""" """
if isinstance(theType, nwItemType): if isinstance(theType, nwItemType):
self.itemType = theType self.itemType = theType
elif theType in nwItemType.__members__: elif isItemType(theType):
self.itemType = nwItemType[theType] self.itemType = nwItemType[theType]
else: else:
logger.error("Unrecognised item type '%s'" % theType) logger.error("Unrecognised item type '%s'" % theType)
@@ -214,7 +216,7 @@ class NWItem():
""" """
if isinstance(theClass, nwItemClass): if isinstance(theClass, nwItemClass):
self.itemClass = theClass self.itemClass = theClass
elif theClass in nwItemClass.__members__: elif isItemClass(theClass):
self.itemClass = nwItemClass[theClass] self.itemClass = nwItemClass[theClass]
else: else:
logger.error("Unrecognised item class '%s'" % theClass) logger.error("Unrecognised item class '%s'" % theClass)
@@ -227,7 +229,7 @@ class NWItem():
""" """
if isinstance(theLayout, nwItemLayout): if isinstance(theLayout, nwItemLayout):
self.itemLayout = theLayout self.itemLayout = theLayout
elif theLayout in nwItemLayout.__members__: elif isItemLayout(theLayout):
self.itemLayout = nwItemLayout[theLayout] self.itemLayout = nwItemLayout[theLayout]
else: else:
logger.error("Unrecognised item layout '%s'" % theLayout) logger.error("Unrecognised item layout '%s'" % theLayout)
+5 -4
View File
@@ -25,6 +25,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw
import logging import logging
import json import json
import os import os
@@ -121,9 +122,9 @@ class OptionState():
try: try:
with open(stateFile, mode="r", encoding="utf8") as inFile: with open(stateFile, mode="r", encoding="utf8") as inFile:
theState = json.load(inFile) theState = json.load(inFile)
except Exception as e: except Exception:
logger.error("Failed to load GUI options file") logger.error("Failed to load GUI options file")
logger.error(str(e)) nw.logException()
return False return False
# Filter out unused variables # Filter out unused variables
@@ -148,9 +149,9 @@ class OptionState():
try: try:
with open(stateFile, mode="w+", encoding="utf8") as outFile: with open(stateFile, mode="w+", encoding="utf8") as outFile:
json.dump(self.theState, outFile, indent=2) json.dump(self.theState, outFile, indent=2)
except Exception as e: except Exception:
logger.error("Failed to save GUI options file") logger.error("Failed to save GUI options file")
logger.error(str(e)) nw.logException()
return False return False
return True return True
+23 -18
View File
@@ -1219,9 +1219,9 @@ class NWProject():
if len(theLines) != 4: if len(theLines) != 4:
return ["ERROR"] return ["ERROR"]
except Exception as e: except Exception:
logger.error("Failed to read project lockfile") logger.error("Failed to read project lockfile")
logger.error(str(e)) nw.logException()
return ["ERROR"] return ["ERROR"]
return theLines return theLines
@@ -1240,9 +1240,9 @@ class NWProject():
outFile.write("%s\n" % self.mainConf.kernelVer) outFile.write("%s\n" % self.mainConf.kernelVer)
outFile.write("%d\n" % time()) outFile.write("%d\n" % time())
except Exception as e: except Exception:
logger.error("Failed to write project lockfile") logger.error("Failed to write project lockfile")
logger.error(str(e)) nw.logException()
return False return False
return True return True
@@ -1257,9 +1257,9 @@ class NWProject():
if os.path.isfile(lockFile): if os.path.isfile(lockFile):
try: try:
os.unlink(lockFile) os.unlink(lockFile)
except Exception as e: except Exception:
logger.error("Failed to remove project lockfile") logger.error("Failed to remove project lockfile")
logger.error(str(e)) nw.logException()
return False return False
return True return True
@@ -1415,9 +1415,9 @@ class NWProject():
self.notesWCount, self.notesWCount,
)) ))
except Exception as e: except Exception:
logger.error("Failed to write session stats file") logger.error("Failed to write session stats file")
logger.error(str(e)) nw.logException()
return False return False
return True return True
@@ -1453,17 +1453,19 @@ class NWProject():
os.rename(theFile, newPath) os.rename(theFile, newPath)
logger.info("Moved file: %s" % theFile) logger.info("Moved file: %s" % theFile)
logger.info("New location: %s" % newPath) logger.info("New location: %s" % newPath)
except Exception as e: except Exception:
logger.error(str(e))
errList.append("Could not move: %s" % theFile) errList.append("Could not move: %s" % theFile)
logger.error("Could not move: %s" % theFile)
nw.logException()
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"): elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
try: try:
os.unlink(theFile) os.unlink(theFile)
logger.info("Deleted file: %s" % theFile) logger.info("Deleted file: %s" % theFile)
except Exception as e: except Exception:
logger.error(str(e))
errList.append("Could not delete: %s" % theFile) errList.append("Could not delete: %s" % theFile)
logger.error("Could not delete: %s" % theFile)
nw.logException()
else: else:
theErr = self._moveUnknownItem(theData, dataItem) theErr = self._moveUnknownItem(theData, dataItem)
@@ -1475,9 +1477,10 @@ class NWProject():
try: try:
os.rmdir(theData) os.rmdir(theData)
logger.info("Removed folder: %s" % theFolder) logger.info("Removed folder: %s" % theFolder)
except Exception as e: except Exception:
logger.error(str(e))
errList.append("Failed to remove: %s" % theFolder) errList.append("Failed to remove: %s" % theFolder)
logger.error("Failed to remove: %s" % theFolder)
nw.logException()
return errList return errList
@@ -1495,8 +1498,9 @@ class NWProject():
try: try:
os.rename(theSrc, theDst) os.rename(theSrc, theDst)
logger.info("Moved to junk: %s" % theSrc) logger.info("Moved to junk: %s" % theSrc)
except Exception as e: except Exception:
logger.error(str(e)) logger.error("Could not move item %s to junk." % theSrc)
nw.logException()
return "Could not move item %s to junk." % theSrc return "Could not move item %s to junk." % theSrc
return "" return ""
@@ -1529,8 +1533,9 @@ class NWProject():
logger.info("Deleting: %s" % rmFile) logger.info("Deleting: %s" % rmFile)
try: try:
os.unlink(rmFile) os.unlink(rmFile)
except Exception as e: except Exception:
logger.error(str(e)) logger.error("Could not delete: %s" % rmFile)
nw.logException()
return False return False
return True return True
+8 -8
View File
@@ -72,9 +72,9 @@ class NWSpellCheck():
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile: with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
outFile.write("%s\n" % newWord) outFile.write("%s\n" % newWord)
self.projDict.append(newWord) self.projDict.append(newWord)
except Exception as e: except Exception:
logger.error("Failed to add word to project word list %s" % str(self.projectDict)) logger.error("Failed to add word to project word list %s" % str(self.projectDict))
logger.error(str(e)) nw.logException()
return False return False
return True return True
return False return False
@@ -123,9 +123,9 @@ class NWSpellCheck():
if len(theLine) > 0 and theLine not in self.projDict: if len(theLine) > 0 and theLine not in self.projDict:
self.projDict.append(theLine) self.projDict.append(theLine)
logger.debug("Project word list contains %d words" % len(self.projDict)) logger.debug("Project word list contains %d words" % len(self.projDict))
except Exception as e: except Exception:
logger.error("Failed to load project word list") logger.error("Failed to load project word list")
logger.error(str(e)) nw.logException()
return False return False
return True return True
@@ -201,9 +201,9 @@ class NWSpellEnchant(NWSpellCheck):
try: try:
spTag = self.theDict.tag spTag = self.theDict.tag
spName = self.theDict.provider.name spName = self.theDict.provider.name
except Exception as e: except Exception:
logger.error("Failed to extract information about the dictionary") logger.error("Failed to extract information about the dictionary")
logger.error(str(e)) nw.logException()
spTag = "" spTag = ""
spName = "" spName = ""
@@ -261,9 +261,9 @@ class NWSpellSimple(NWSpellCheck):
logger.debug("Spell check word list for language %s loaded" % theLang) logger.debug("Spell check word list for language %s loaded" % theLang)
logger.debug("Word list contains %d words" % len(self.WORDS)) logger.debug("Word list contains %d words" % len(self.WORDS))
self.spellLanguage = theLang self.spellLanguage = theLang
except Exception as e: except Exception:
logger.error("Failed to load spell check word list for language %s" % theLang) logger.error("Failed to load spell check word list for language %s" % theLang)
logger.error(str(e)) nw.logException()
self.spellLanguage = None self.spellLanguage = None
self._readProjectDictionary(projectDict) self._readProjectDictionary(projectDict)
+4 -2
View File
@@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw
import logging import logging
import os import os
@@ -204,8 +205,9 @@ class NWTree():
outFile.write("\n".join(tocList)) outFile.write("\n".join(tocList))
outFile.write("\n") outFile.write("\n")
except Exception as e: except Exception:
logger.error(str(e)) logger.error("Could not write ToC file")
nw.logException()
return False return False
return True return True
+19 -4
View File
@@ -24,12 +24,31 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import sys
import logging
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel, qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
QDialogButtonBox QDialogButtonBox
) )
logger = logging.getLogger(__name__)
# =============================================================================================== #
# Utility Functions
# =============================================================================================== #
def logException():
"""Log the content of an exception message.
"""
exType, exValue, _ = sys.exc_info()
logger.error("%s: %s" % (exType.__name__, str(exValue).strip("'")))
# =============================================================================================== #
# Error Handler
# =============================================================================================== #
class NWErrorMessage(QDialog): class NWErrorMessage(QDialog):
def __init__(self, parent): def __init__(self, parent):
@@ -72,7 +91,6 @@ class NWErrorMessage(QDialog):
"""Generate a message and append session data, error info and """Generate a message and append session data, error info and
error traceback. error traceback.
""" """
import sys
from traceback import format_tb from traceback import format_tb
from nw import __issuesurl__, __version__ from nw import __issuesurl__, __version__
from PyQt5.Qt import PYQT_VERSION_STR from PyQt5.Qt import PYQT_VERSION_STR
@@ -133,15 +151,12 @@ class NWErrorMessage(QDialog):
# END Class NWErrorMessage # END Class NWErrorMessage
def exceptionHandler(exType, exValue, exTrace): def exceptionHandler(exType, exValue, exTrace):
"""Function to catch unhandled global exceptions. """Function to catch unhandled global exceptions.
""" """
import logging
from traceback import print_tb from traceback import print_tb
from PyQt5.QtWidgets import qApp from PyQt5.QtWidgets import qApp
logger = logging.getLogger(__name__)
logger.critical("%s: %s" % (exType.__name__, str(exValue))) logger.critical("%s: %s" % (exType.__name__, str(exValue)))
print_tb(exTrace) print_tb(exTrace)
+6 -6
View File
@@ -671,9 +671,9 @@ class GuiBuildNovel(QDialog):
bldObj.doConvert() bldObj.doConvert()
bldObj.doPostProcessing() bldObj.doPostProcessing()
except Exception as e: except Exception:
logger.error("Failed to generate html of document '%s'" % tItem.itemHandle) logger.error("Failed to generate html of document '%s'" % tItem.itemHandle)
logger.error(str(e)) nw.logException()
if isPreview: if isPreview:
self.docView.setText(( self.docView.setText((
"Failed to generate preview. " "Failed to generate preview. "
@@ -997,9 +997,9 @@ class GuiBuildNovel(QDialog):
with open(buildCache, mode="r", encoding="utf8") as inFile: with open(buildCache, mode="r", encoding="utf8") as inFile:
theJson = inFile.read() theJson = inFile.read()
theData = json.loads(theJson) theData = json.loads(theJson)
except Exception as e: except Exception:
logger.error("Failed to load build cache") logger.error("Failed to load build cache")
logger.error(str(e)) nw.logException()
return False return False
if "htmlText" in theData.keys(): if "htmlText" in theData.keys():
@@ -1026,9 +1026,9 @@ class GuiBuildNovel(QDialog):
"htmlStyle" : self.htmlStyle, "htmlStyle" : self.htmlStyle,
"buildTime" : self.buildTime, "buildTime" : self.buildTime,
}, indent=2)) }, indent=2))
except Exception as e: except Exception:
logger.error("Failed to save build cache") logger.error("Failed to save build cache")
logger.error(str(e)) nw.logException()
return False return False
return True return True
+2 -2
View File
@@ -180,9 +180,9 @@ class GuiDocViewer(QTextBrowser):
aDoc.tokenizeText() aDoc.tokenizeText()
aDoc.doConvert() aDoc.doConvert()
aDoc.doPostProcessing() aDoc.doPostProcessing()
except Exception as e: except Exception:
logger.error("Failed to generate preview for document with handle '%s'" % tHandle) logger.error("Failed to generate preview for document with handle '%s'" % tHandle)
logger.error(str(e)) nw.logException()
self.setText("An error occurred while generating the preview.") self.setText("An error occurred while generating the preview.")
return False return False
+8 -8
View File
@@ -265,9 +265,9 @@ class GuiTheme:
if os.path.isfile(self.cssFile): if os.path.isfile(self.cssFile):
with open(self.cssFile, mode="r", encoding="utf8") as inFile: with open(self.cssFile, mode="r", encoding="utf8") as inFile:
cssData = inFile.read() cssData = inFile.read()
except Exception as e: except Exception:
logger.error("Could not load theme css file") logger.error("Could not load theme css file")
logger.error(str(e)) nw.logException()
return False return False
# Config File # Config File
@@ -275,9 +275,9 @@ class GuiTheme:
try: try:
with open(self.confFile, mode="r", encoding="utf8") as inFile: with open(self.confFile, mode="r", encoding="utf8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception as e: except Exception:
logger.error("Could not load theme settings from: %s" % self.confFile) logger.error("Could not load theme settings from: %s" % self.confFile)
logger.error(str(e)) nw.logException()
return False return False
## Main ## Main
@@ -333,9 +333,9 @@ class GuiTheme:
try: try:
with open(self.syntaxFile, mode="r", encoding="utf8") as inFile: with open(self.syntaxFile, mode="r", encoding="utf8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception as e: except Exception:
logger.error("Could not load syntax colours from: %s" % self.syntaxFile) logger.error("Could not load syntax colours from: %s" % self.syntaxFile)
logger.error(str(e)) nw.logException()
return False return False
## Main ## Main
@@ -637,9 +637,9 @@ class GuiIcons:
try: try:
with open(self.confFile, mode="r", encoding="utf8") as inFile: with open(self.confFile, mode="r", encoding="utf8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception as e: except Exception:
logger.error("Could not load icon theme settings from: %s" % self.confFile) logger.error("Could not load icon theme settings from: %s" % self.confFile)
logger.error(str(e)) nw.logException()
return False return False
## Main ## Main
+91 -1
View File
@@ -26,7 +26,8 @@ import pytest
from nw.common import ( from nw.common import (
checkString, checkBool, checkInt, colRange, formatInt, transferCase, checkString, checkBool, checkInt, colRange, formatInt, transferCase,
fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt, fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt,
makeFileNameSafe makeFileNameSafe, isHandle, isTitleTag, isItemClass, isItemType,
isItemLayout
) )
from tools import cmpList from tools import cmpList
@@ -88,6 +89,95 @@ def testBaseCommon_CheckHandle():
# END Test testBaseCommon_CheckHandle # END Test testBaseCommon_CheckHandle
@pytest.mark.base
def testBaseCommon_IsHandle():
"""Test the isHandle function.
"""
assert isHandle("47666c91c7ccf")
assert not isHandle("47666C91C7CCF")
assert not isHandle("h7666c91c7ccf")
assert not isHandle("None")
assert not isHandle(None)
assert not isHandle("STUFF")
# END Test testBaseCommon_IsHandle
@pytest.mark.base
def testBaseCommon_IsTitleTag():
"""Test the isItemClass function.
"""
assert isTitleTag("T123456")
assert not isTitleTag("t123456")
assert not isTitleTag("S123456")
assert not isTitleTag("T12345A")
assert not isTitleTag("T1234567")
assert not isTitleTag("None")
assert not isTitleTag(None)
assert not isTitleTag("STUFF")
# END Test testBaseCommon_IsTitleTag
@pytest.mark.base
def testBaseCommon_IsItemClass():
"""Test the isItemClass function.
"""
assert isItemClass("NO_CLASS")
assert isItemClass("NOVEL")
assert isItemClass("PLOT")
assert isItemClass("CHARACTER")
assert isItemClass("WORLD")
assert isItemClass("TIMELINE")
assert isItemClass("OBJECT")
assert isItemClass("ENTITY")
assert isItemClass("CUSTOM")
assert isItemClass("ARCHIVE")
assert isItemClass("TRASH")
assert not isItemClass("None")
assert not isItemClass(None)
assert not isItemClass("STUFF")
# END Test testBaseCommon_IsItemClass
@pytest.mark.base
def testBaseCommon_IsItemType():
"""Test the isItemType function.
"""
assert isItemType("NO_TYPE")
assert isItemType("ROOT")
assert isItemType("FOLDER")
assert isItemType("FILE")
assert isItemType("TRASH")
assert not isItemType("None")
assert not isItemType(None)
assert not isItemType("STUFF")
# END Test testBaseCommon_IsItemType
@pytest.mark.base
def testBaseCommon_IsItemLayout():
"""Test the isItemLayout function.
"""
assert isItemLayout("NO_LAYOUT")
assert isItemLayout("TITLE")
assert isItemLayout("BOOK")
assert isItemLayout("PAGE")
assert isItemLayout("PARTITION")
assert isItemLayout("UNNUMBERED")
assert isItemLayout("CHAPTER")
assert isItemLayout("SCENE")
assert isItemLayout("NOTE")
assert not isItemLayout("None")
assert not isItemLayout(None)
assert not isItemLayout("STUFF")
# END Test testBaseCommon_IsItemLayout
@pytest.mark.base @pytest.mark.base
def testBaseCommon_HexToInt(): def testBaseCommon_HexToInt():
"""Test the hexToInt function. """Test the hexToInt function.
+501 -32
View File
@@ -115,38 +115,7 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
# Break the index and check that we notice # Break the index and check that we notice
assert not theIndex.indexBroken assert not theIndex.indexBroken
theIndex._tagIndex["Bod"].append("Stuff") # No longer len() == 4 theIndex._tagIndex["Bod"].append("Stuff")
theIndex.checkIndex()
assert theIndex.indexBroken
assert theIndex.loadIndex()
assert not theIndex.indexBroken
theIndex._refIndex["fb609cd8319dc"]["T000001"]["tags"].append("Stuff") # No longer len() == 3
theIndex.checkIndex()
assert theIndex.indexBroken
assert theIndex.loadIndex()
assert not theIndex.indexBroken
theIndex._novelIndex["7a992350f3eb6"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
theIndex.checkIndex()
assert theIndex.indexBroken
assert theIndex.loadIndex()
assert not theIndex.indexBroken
theIndex._noteIndex["4c4f28287af27"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
theIndex.checkIndex()
assert theIndex.indexBroken
assert theIndex.loadIndex()
assert not theIndex.indexBroken
theIndex._textCounts["7a992350f3eb6"].append("Stuff") # No longer len() == 3
theIndex.checkIndex()
assert theIndex.indexBroken
# Make the try/except trigger as well
assert theIndex.loadIndex()
assert not theIndex.indexBroken
theIndex._refIndex["fb609cd8319dc"]["T000001"] = {"tagssss": []} # Wrong key name
theIndex.checkIndex() theIndex.checkIndex()
assert theIndex.indexBroken assert theIndex.indexBroken
@@ -676,3 +645,503 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
assert theProject.closeProject() assert theProject.closeProject()
# END Test testCoreIndex_ExtractData # END Test testCoreIndex_ExtractData
@pytest.mark.core
def testCoreIndex_CheckTagIndex(dummyGUI):
"""Test the tag index checker.
"""
theProject = NWProject(dummyGUI)
theIndex = NWIndex(theProject, dummyGUI)
# Valid Index
theIndex._tagIndex = {
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
"Jane": [3, "bb2c23b3c42cc", "CHARACTER", "T000001"],
}
assert theIndex._checkTagIndex() is None
# Wrong Key Type
theIndex._tagIndex = {
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
123456: [3, "bb2c23b3c42cc", "CHARACTER", "T000001"],
}
with pytest.raises(KeyError):
theIndex._checkTagIndex()
# Wrong Length
theIndex._tagIndex = {
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
"Jane": [3, "bb2c23b3c42cc", "CHARACTER", "T000001", "Stuff"],
}
with pytest.raises(IndexError):
theIndex._checkTagIndex()
# Wrong Type of Entry 0
theIndex._tagIndex = {
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
"Jane": ["3", "bb2c23b3c42cc", "CHARACTER", "T000001"],
}
with pytest.raises(ValueError):
theIndex._checkTagIndex()
# Wrong Type of Entry 1
theIndex._tagIndex = {
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
"Jane": [3, 0xbb2c23b3c42cc, "CHARACTER", "T000001"],
}
with pytest.raises(ValueError):
theIndex._checkTagIndex()
# Wrong Type of Entry 2
theIndex._tagIndex = {
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
"Jane": [3, "bb2c23b3c42cc", "INVALID_CLASS", "T000001"],
}
with pytest.raises(ValueError):
theIndex._checkTagIndex()
# Wrong Type of Entry 3
theIndex._tagIndex = {
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
"Jane": [3, "bb2c23b3c42cc", "CHARACTER", "INVALID"],
}
with pytest.raises(ValueError):
theIndex._checkTagIndex()
# END Test testCoreIndex_CheckTagIndex
@pytest.mark.core
def testCoreIndex_CheckRefIndex(dummyGUI):
"""Test the reference index checker.
"""
theProject = NWProject(dummyGUI)
theIndex = NWIndex(theProject, dummyGUI)
# Valid Index
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"tags": [
[3, "@pov", "Jane"], [4, "@location", "Earth"]
], "updated": 1611922868}
}
}
assert theIndex._checkRefIndex() is None
# Invalid Handle
theIndex._refIndex = {
"Ha2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"tags": [
[3, "@pov", "Jane"], [4, "@location", "Earth"]
], "updated": 1611922868}
}
}
with pytest.raises(KeyError):
theIndex._checkRefIndex()
# Invalid Title
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"INVALID": {"tags": [
[3, "@pov", "Jane"], [4, "@location", "Earth"]
], "updated": 1611922868}
}
}
with pytest.raises(KeyError):
theIndex._checkRefIndex()
# Missing 'tags'
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"updated": 1611922868}
}
}
with pytest.raises(KeyError):
theIndex._checkRefIndex()
# Wrong Length of 'tags'
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"tags": [
[3, "@pov", "Jane"], [4, "@location", "Earth", "Stuff"]
], "updated": 1611922868}
}
}
with pytest.raises(IndexError):
theIndex._checkRefIndex()
# Wrong Type of 'tags' Entry 0
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"tags": [
[3, "@pov", "Jane"], ["4", "@location", "Earth"]
], "updated": 1611922868}
}
}
with pytest.raises(ValueError):
theIndex._checkRefIndex()
# Wrong Type of 'tags' Entry 1
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"tags": [
[3, "@pov", "Jane"], [4, "@stuff", "Earth"]
], "updated": 1611922868}
}
}
with pytest.raises(ValueError):
theIndex._checkRefIndex()
# Wrong Type of 'tags' Entry 1
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"tags": [
[3, "@pov", "Jane"], [4, "@location", 123456]
], "updated": 1611922868}
}
}
with pytest.raises(ValueError):
theIndex._checkRefIndex()
# Missing 'updated'
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"tags": []}
}
}
with pytest.raises(KeyError):
theIndex._checkRefIndex()
# Wrong Type of 'updated' Entry 1
theIndex._refIndex = {
"6a2d6d5f4f401": {
"T000000": {"tags": [], "updated": 1611922868},
"T000001": {"tags": [], "updated": "1611922868"}
}
}
with pytest.raises(ValueError):
theIndex._checkRefIndex()
# END Test testCoreIndex_CheckRefIndex
@pytest.mark.core
def testCoreIndex_CheckNovelNoteIndex(dummyGUI):
"""Test the novel and note index checkers.
"""
theProject = NWProject(dummyGUI)
theIndex = NWIndex(theProject, dummyGUI)
# Valid Index
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
theIndex._noteIndex = theIndex._novelIndex.copy()
assert theIndex._checkNovelNoteIndex("novelIndex") is None
assert theIndex._checkNovelNoteIndex("noteIndex") is None
with pytest.raises(IndexError):
theIndex._checkNovelNoteIndex("notAnIndex")
# Invalid Handle
theIndex._novelIndex = {
"H3b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Invalid Title
theIndex._novelIndex = {
"53b69b83cdafc": {
"INVALID": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Length
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868, "stuff": None
}
}
}
with pytest.raises(IndexError):
theIndex._checkNovelNoteIndex("novelIndex")
# Missing Keys
# ============
# Missing 'level'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"stuff": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Missing 'title'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "stuff": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Missing 'layout'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "stuff": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Missing 'synopsis'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "stuff": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Missing 'cCount'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"stuff": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Missing 'wCount'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "stuff": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Missing 'pCount'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "stuff": 2, "updated": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Missing 'updated'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "stuff": 1611922868
}
}
}
with pytest.raises(KeyError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Types
# ===========
# Wrong Type for 'level'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "XX", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(ValueError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Type for 'title'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": 12345678, "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(ValueError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Type for 'layout'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "INVALID", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(ValueError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Type for 'synopsis'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": 123456,
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(ValueError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Type for 'cCount'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": "72", "wCount": 15, "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(ValueError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Type for 'wCount'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": "15", "pCount": 2, "updated": 1611922868
}
}
}
with pytest.raises(ValueError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Type for 'pCount'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": "2", "updated": 1611922868
}
}
}
with pytest.raises(ValueError):
theIndex._checkNovelNoteIndex("novelIndex")
# Wrong Type for 'updated'
theIndex._novelIndex = {
"53b69b83cdafc": {
"T000001": {
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
"cCount": 72, "wCount": 15, "pCount": 2, "updated": "1611922868"
}
}
}
with pytest.raises(ValueError):
theIndex._checkNovelNoteIndex("novelIndex")
# END Test testCoreIndex_CheckNovelNoteIndex
@pytest.mark.core
def testCoreIndex_CheckTextCounts(dummyGUI):
"""Test the text counts checker.
"""
theProject = NWProject(dummyGUI)
theIndex = NWIndex(theProject, dummyGUI)
# Valid Index
theIndex._textCounts = {
"53b69b83cdafc": [72, 15, 2],
"974e400180a99": [210, 40, 2],
}
assert theIndex._checkTextCounts() is None
# Invalid Handle
theIndex._textCounts = {
"53b69b83cdafc": [72, 15, 2],
"h74e400180a99": [210, 40, 2],
}
with pytest.raises(KeyError):
theIndex._checkTextCounts()
# Wrong Length
theIndex._textCounts = {
"53b69b83cdafc": [72, 15, 2],
"974e400180a99": [210, 40, 2, 8],
}
with pytest.raises(IndexError):
theIndex._checkTextCounts()
# Type of Entry 0
theIndex._textCounts = {
"53b69b83cdafc": [72, 15, 2],
"974e400180a99": ["210", 40, 2],
}
with pytest.raises(ValueError):
theIndex._checkTextCounts()
# Type of Entry 1
theIndex._textCounts = {
"53b69b83cdafc": [72, 15, 2],
"974e400180a99": [210, "40", 2],
}
with pytest.raises(ValueError):
theIndex._checkTextCounts()
# Type of Entry 2
theIndex._textCounts = {
"53b69b83cdafc": [72, 15, 2],
"974e400180a99": [210, 40, "2"],
}
with pytest.raises(ValueError):
theIndex._checkTextCounts()
# END Test testCoreIndex_CheckTextCounts