From 4e7888259f7d2ed5afb0161164d9c5369086f254 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 29 Jan 2021 13:17:31 +0100
Subject: [PATCH 1/5] Add logging wrapper for exceptions
---
nw/__init__.py | 4 ++--
nw/error.py | 23 +++++++++++++++++++----
2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/nw/__init__.py b/nw/__init__.py
index 02ac7162..2ee82184 100644
--- a/nw/__init__.py
+++ b/nw/__init__.py
@@ -32,7 +32,7 @@ import logging
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QErrorMessage
-from nw.error import exceptionHandler
+from nw.error import exceptionHandler, logException
from nw.config import Config
##
@@ -270,7 +270,7 @@ def main(sysArgs=None):
info["CFBundleName"] = "novelWriter"
except ImportError as e:
logger.error("Failed to set application name")
- logger.error(str(e))
+ logException(e)
# Import GUI (after dependency checks), and launch
from nw.guimain import GuiMain
diff --git a/nw/error.py b/nw/error.py
index 1f879e37..ecfc3b77 100644
--- a/nw/error.py
+++ b/nw/error.py
@@ -24,12 +24,31 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import sys
+import logging
+
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
QDialogButtonBox
)
+logger = logging.getLogger(__name__)
+
+# =============================================================================================== #
+# Utility Functions
+# =============================================================================================== #
+
+def logException(exObj):
+ """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):
def __init__(self, parent):
@@ -72,7 +91,6 @@ class NWErrorMessage(QDialog):
"""Generate a message and append session data, error info and
error traceback.
"""
- import sys
from traceback import format_tb
from nw import __issuesurl__, __version__
from PyQt5.Qt import PYQT_VERSION_STR
@@ -133,15 +151,12 @@ class NWErrorMessage(QDialog):
# END Class NWErrorMessage
-
def exceptionHandler(exType, exValue, exTrace):
"""Function to catch unhandled global exceptions.
"""
- import logging
from traceback import print_tb
from PyQt5.QtWidgets import qApp
- logger = logging.getLogger(__name__)
logger.critical("%s: %s" % (exType.__name__, str(exValue)))
print_tb(exTrace)
From 286efade590289345cef3cca1ab80de3be08e3c4 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 29 Jan 2021 13:22:38 +0100
Subject: [PATCH 2/5] Add more check functions
---
nw/common.py | 38 ++++++++++--
tests/test_base/test_base_common.py | 92 ++++++++++++++++++++++++++++-
2 files changed, 125 insertions(+), 5 deletions(-)
diff --git a/nw/common.py b/nw/common.py
index 11546c88..826f7370 100644
--- a/nw/common.py
+++ b/nw/common.py
@@ -30,7 +30,9 @@ from datetime import datetime
from PyQt5.QtWidgets import qApp
-from nw.constants import nwConst, nwUnicode
+from nw.constants import (
+ nwConst, nwUnicode, nwItemClass, nwItemType, nwItemLayout
+)
logger = logging.getLogger(__name__)
@@ -103,11 +105,39 @@ def isHandle(theString):
return False
if len(theString) != 13:
return False
- invalidChar = False
for c in theString:
if c not in "0123456789abcdef":
- invalidChar = True
- return not invalidChar
+ return False
+ 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):
"""Convert a hex string to an integer.
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index 79b6562a..4b076a64 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -26,7 +26,8 @@ import pytest
from nw.common import (
checkString, checkBool, checkInt, colRange, formatInt, transferCase,
fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt,
- makeFileNameSafe
+ makeFileNameSafe, isHandle, isTitleTag, isItemClass, isItemType,
+ isItemLayout
)
from tools import cmpList
@@ -88,6 +89,95 @@ def 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
def testBaseCommon_HexToInt():
"""Test the hexToInt function.
From a2f223a9928aa84205c1093e11064bd711fa4ee0 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 29 Jan 2021 18:15:00 +0100
Subject: [PATCH 3/5] Perform a detailed check of the loaded index to make sure
everything is in order
---
nw/core/index.py | 184 ++++++++--
tests/test_core/test_core_index.py | 533 +++++++++++++++++++++++++++--
2 files changed, 660 insertions(+), 57 deletions(-)
diff --git a/nw/core/index.py b/nw/core/index.py
index ba2e06f3..37cddebd 100644
--- a/nw/core/index.py
+++ b/nw/core/index.py
@@ -36,11 +36,13 @@ from nw.constants import (
)
from nw.core.document import NWDoc
from nw.core.tools import countWords
+from nw.common import isHandle, isTitleTag, isItemClass, isItemLayout
logger = logging.getLogger(__name__)
class NWIndex():
+ H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
def __init__(self, theProject, theParent):
@@ -155,6 +157,11 @@ class NWIndex():
except Exception as e:
logger.error("Failed to load index file")
logger.error(str(e))
+ self.indexBroken = True
+ self.theParent.makeAlert(
+ "Could not load cached index file. Rebuilding index.",
+ nwAlert.WARN
+ )
return False
self._tagIndex = theData.get("tagIndex", {})
@@ -200,37 +207,25 @@ class NWIndex():
elements it should.
"""
logger.debug("Checking index")
- self.indexBroken = False
+ tStart = time()
try:
- for tTag in self._tagIndex:
- if len(self._tagIndex[tTag]) != 4:
- self.indexBroken = True
+ self._checkTagIndex()
+ self._checkRefIndex()
+ self._checkNovelNoteIndex("novelIndex")
+ self._checkNovelNoteIndex("noteIndex")
+ self._checkTextCounts()
+ self.indexBroken = False
- for tHandle in self._refIndex:
- for sTitle in self._refIndex[tHandle]:
- 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
-
- except Exception:
+ except Exception as e:
+ logger.error("Error while checking index")
+ nw.logException(e)
self.indexBroken = True
+ tEnd = time()
+ logger.debug("Index check took %.3f ms" % ((tEnd - tStart)*1000))
logger.debug("Index check complete")
+
if self.indexBroken:
self.clearIndex()
self.theParent.makeAlert(
@@ -745,4 +740,143 @@ class NWIndex():
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
diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py
index e10000d6..5e1e9223 100644
--- a/tests/test_core/test_core_index.py
+++ b/tests/test_core/test_core_index.py
@@ -115,38 +115,7 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
# Break the index and check that we notice
assert not theIndex.indexBroken
- theIndex._tagIndex["Bod"].append("Stuff") # No longer len() == 4
- 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._tagIndex["Bod"].append("Stuff")
theIndex.checkIndex()
assert theIndex.indexBroken
@@ -676,3 +645,503 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
assert theProject.closeProject()
# 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
From 89ba4aeeaf413c5aa7a5e8a6b8d0f81ebff05581 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 29 Jan 2021 18:29:50 +0100
Subject: [PATCH 4/5] Use the logException function in all places where
exception messages are written to the error log
---
nw/__init__.py | 4 ++--
nw/config.py | 13 +++++++------
nw/core/index.py | 12 ++++++------
nw/core/options.py | 9 +++++----
nw/core/project.py | 41 +++++++++++++++++++++++------------------
nw/core/spellcheck.py | 16 ++++++++--------
nw/core/tree.py | 6 ++++--
nw/error.py | 2 +-
nw/gui/build.py | 12 ++++++------
nw/gui/docviewer.py | 4 ++--
nw/gui/theme.py | 16 ++++++++--------
11 files changed, 72 insertions(+), 63 deletions(-)
diff --git a/nw/__init__.py b/nw/__init__.py
index 2ee82184..8cb87d08 100644
--- a/nw/__init__.py
+++ b/nw/__init__.py
@@ -268,9 +268,9 @@ def main(sysArgs=None):
bundle = NSBundle.mainBundle()
info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
info["CFBundleName"] = "novelWriter"
- except ImportError as e:
+ except ImportError:
logger.error("Failed to set application name")
- logException(e)
+ logException()
# Import GUI (after dependency checks), and launch
from nw.guimain import GuiMain
diff --git a/nw/config.py b/nw/config.py
index d35582fb..1a9aade2 100644
--- a/nw/config.py
+++ b/nw/config.py
@@ -38,6 +38,7 @@ from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
from nw.constants import nwConst, nwFiles, nwUnicode
from nw.common import splitVersionNumber, formatTimeStamp
+from nw.error import logException
logger = logging.getLogger(__name__)
@@ -293,7 +294,7 @@ class Config:
os.mkdir(self.confPath)
except Exception as e:
logger.error("Could not create folder: %s" % self.confPath)
- logger.error(str(e))
+ logException()
self.hasError = True
self.errData.append("Could not create folder: %s" % self.confPath)
self.errData.append(str(e))
@@ -316,7 +317,7 @@ class Config:
os.mkdir(self.dataPath)
except Exception as e:
logger.error("Could not create folder: %s" % self.dataPath)
- logger.error(str(e))
+ logException()
self.hasError = True
self.errData.append("Could not create folder: %s" % self.dataPath)
self.errData.append(str(e))
@@ -361,7 +362,7 @@ class Config:
cnfParse.read_file(inFile)
except Exception as e:
logger.error("Could not load config file")
- logger.error(str(e))
+ logException()
self.hasError = True
self.errData.append("Could not load config file")
self.errData.append(str(e))
@@ -702,7 +703,7 @@ class Config:
self.confChanged = False
except Exception as e:
logger.error("Could not save config file")
- logger.error(str(e))
+ logException()
self.hasError = True
self.errData.append("Could not save config file")
self.errData.append(str(e))
@@ -978,9 +979,9 @@ class Config:
return self._unpackList(
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(str(e))
+ logException()
return cnfDefault
return cnfDefault
diff --git a/nw/core/index.py b/nw/core/index.py
index 37cddebd..7a9c88bc 100644
--- a/nw/core/index.py
+++ b/nw/core/index.py
@@ -154,9 +154,9 @@ class NWIndex():
try:
with open(indexFile, mode="r", encoding="utf8") as inFile:
theData = json.load(inFile)
- except Exception as e:
+ except Exception:
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.",
@@ -195,9 +195,9 @@ class NWIndex():
"noteIndex" : self._noteIndex,
"textCounts" : self._textCounts,
}, outFile, indent=2)
- except Exception as e:
+ except Exception:
logger.error("Failed to save index file")
- logger.error(str(e))
+ nw.logException()
return False
return True
@@ -217,9 +217,9 @@ class NWIndex():
self._checkTextCounts()
self.indexBroken = False
- except Exception as e:
+ except Exception:
logger.error("Error while checking index")
- nw.logException(e)
+ nw.logException()
self.indexBroken = True
tEnd = time()
diff --git a/nw/core/options.py b/nw/core/options.py
index 36da3f32..c0df1855 100644
--- a/nw/core/options.py
+++ b/nw/core/options.py
@@ -25,6 +25,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import nw
import logging
import json
import os
@@ -121,9 +122,9 @@ class OptionState():
try:
with open(stateFile, mode="r", encoding="utf8") as inFile:
theState = json.load(inFile)
- except Exception as e:
+ except Exception:
logger.error("Failed to load GUI options file")
- logger.error(str(e))
+ nw.logException()
return False
# Filter out unused variables
@@ -148,9 +149,9 @@ class OptionState():
try:
with open(stateFile, mode="w+", encoding="utf8") as outFile:
json.dump(self.theState, outFile, indent=2)
- except Exception as e:
+ except Exception:
logger.error("Failed to save GUI options file")
- logger.error(str(e))
+ nw.logException()
return False
return True
diff --git a/nw/core/project.py b/nw/core/project.py
index a22f7aa9..90d3649f 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -1219,9 +1219,9 @@ class NWProject():
if len(theLines) != 4:
return ["ERROR"]
- except Exception as e:
+ except Exception:
logger.error("Failed to read project lockfile")
- logger.error(str(e))
+ nw.logException()
return ["ERROR"]
return theLines
@@ -1240,9 +1240,9 @@ class NWProject():
outFile.write("%s\n" % self.mainConf.kernelVer)
outFile.write("%d\n" % time())
- except Exception as e:
+ except Exception:
logger.error("Failed to write project lockfile")
- logger.error(str(e))
+ nw.logException()
return False
return True
@@ -1257,9 +1257,9 @@ class NWProject():
if os.path.isfile(lockFile):
try:
os.unlink(lockFile)
- except Exception as e:
+ except Exception:
logger.error("Failed to remove project lockfile")
- logger.error(str(e))
+ nw.logException()
return False
return True
@@ -1415,9 +1415,9 @@ class NWProject():
self.notesWCount,
))
- except Exception as e:
+ except Exception:
logger.error("Failed to write session stats file")
- logger.error(str(e))
+ nw.logException()
return False
return True
@@ -1453,17 +1453,19 @@ class NWProject():
os.rename(theFile, newPath)
logger.info("Moved file: %s" % theFile)
logger.info("New location: %s" % newPath)
- except Exception as e:
- logger.error(str(e))
+ except Exception:
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"):
try:
os.unlink(theFile)
logger.info("Deleted file: %s" % theFile)
- except Exception as e:
- logger.error(str(e))
+ except Exception:
errList.append("Could not delete: %s" % theFile)
+ logger.error("Could not delete: %s" % theFile)
+ nw.logException()
else:
theErr = self._moveUnknownItem(theData, dataItem)
@@ -1475,9 +1477,10 @@ class NWProject():
try:
os.rmdir(theData)
logger.info("Removed folder: %s" % theFolder)
- except Exception as e:
- logger.error(str(e))
+ except Exception:
errList.append("Failed to remove: %s" % theFolder)
+ logger.error("Failed to remove: %s" % theFolder)
+ nw.logException()
return errList
@@ -1495,8 +1498,9 @@ class NWProject():
try:
os.rename(theSrc, theDst)
logger.info("Moved to junk: %s" % theSrc)
- except Exception as e:
- logger.error(str(e))
+ except Exception:
+ logger.error("Could not move item %s to junk." % theSrc)
+ nw.logException()
return "Could not move item %s to junk." % theSrc
return ""
@@ -1529,8 +1533,9 @@ class NWProject():
logger.info("Deleting: %s" % rmFile)
try:
os.unlink(rmFile)
- except Exception as e:
- logger.error(str(e))
+ except Exception:
+ logger.error("Could not delete: %s" % rmFile)
+ nw.logException()
return False
return True
diff --git a/nw/core/spellcheck.py b/nw/core/spellcheck.py
index 8b3807fa..8dd33bbe 100644
--- a/nw/core/spellcheck.py
+++ b/nw/core/spellcheck.py
@@ -72,9 +72,9 @@ class NWSpellCheck():
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
outFile.write("%s\n" % 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(str(e))
+ nw.logException()
return False
return True
return False
@@ -123,9 +123,9 @@ class NWSpellCheck():
if len(theLine) > 0 and theLine not in self.projDict:
self.projDict.append(theLine)
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(str(e))
+ nw.logException()
return False
return True
@@ -201,9 +201,9 @@ class NWSpellEnchant(NWSpellCheck):
try:
spTag = self.theDict.tag
spName = self.theDict.provider.name
- except Exception as e:
+ except Exception:
logger.error("Failed to extract information about the dictionary")
- logger.error(str(e))
+ nw.logException()
spTag = ""
spName = ""
@@ -261,9 +261,9 @@ class NWSpellSimple(NWSpellCheck):
logger.debug("Spell check word list for language %s loaded" % theLang)
logger.debug("Word list contains %d words" % len(self.WORDS))
self.spellLanguage = theLang
- except Exception as e:
+ except Exception:
logger.error("Failed to load spell check word list for language %s" % theLang)
- logger.error(str(e))
+ nw.logException()
self.spellLanguage = None
self._readProjectDictionary(projectDict)
diff --git a/nw/core/tree.py b/nw/core/tree.py
index 36a56e5c..94e9b97a 100644
--- a/nw/core/tree.py
+++ b/nw/core/tree.py
@@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import nw
import logging
import os
@@ -179,8 +180,9 @@ class NWTree():
outFile.write("\n".join(tocList))
outFile.write("\n")
- except Exception as e:
- logger.error(str(e))
+ except Exception:
+ logger.error("Could not write ToC file")
+ nw.logException()
return False
return True
diff --git a/nw/error.py b/nw/error.py
index ecfc3b77..998b7347 100644
--- a/nw/error.py
+++ b/nw/error.py
@@ -39,7 +39,7 @@ logger = logging.getLogger(__name__)
# Utility Functions
# =============================================================================================== #
-def logException(exObj):
+def logException():
"""Log the content of an exception message.
"""
exType, exValue, _ = sys.exc_info()
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 5bcea455..18bb4dc7 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -671,9 +671,9 @@ class GuiBuildNovel(QDialog):
bldObj.doConvert()
bldObj.doPostProcessing()
- except Exception as e:
+ except Exception:
logger.error("Failed to generate html of document '%s'" % tItem.itemHandle)
- logger.error(str(e))
+ nw.logException()
if isPreview:
self.docView.setText((
"Failed to generate preview. "
@@ -997,9 +997,9 @@ class GuiBuildNovel(QDialog):
with open(buildCache, mode="r", encoding="utf8") as inFile:
theJson = inFile.read()
theData = json.loads(theJson)
- except Exception as e:
+ except Exception:
logger.error("Failed to load build cache")
- logger.error(str(e))
+ nw.logException()
return False
if "htmlText" in theData.keys():
@@ -1026,9 +1026,9 @@ class GuiBuildNovel(QDialog):
"htmlStyle" : self.htmlStyle,
"buildTime" : self.buildTime,
}, indent=2))
- except Exception as e:
+ except Exception:
logger.error("Failed to save build cache")
- logger.error(str(e))
+ nw.logException()
return False
return True
diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py
index 55e458bb..24d53019 100644
--- a/nw/gui/docviewer.py
+++ b/nw/gui/docviewer.py
@@ -180,9 +180,9 @@ class GuiDocViewer(QTextBrowser):
aDoc.tokenizeText()
aDoc.doConvert()
aDoc.doPostProcessing()
- except Exception as e:
+ except Exception:
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.")
return False
diff --git a/nw/gui/theme.py b/nw/gui/theme.py
index 8aa0c1fe..73d8a7b0 100644
--- a/nw/gui/theme.py
+++ b/nw/gui/theme.py
@@ -265,9 +265,9 @@ class GuiTheme:
if os.path.isfile(self.cssFile):
with open(self.cssFile, mode="r", encoding="utf8") as inFile:
cssData = inFile.read()
- except Exception as e:
+ except Exception:
logger.error("Could not load theme css file")
- logger.error(str(e))
+ nw.logException()
return False
# Config File
@@ -275,9 +275,9 @@ class GuiTheme:
try:
with open(self.confFile, mode="r", encoding="utf8") as inFile:
confParser.read_file(inFile)
- except Exception as e:
+ except Exception:
logger.error("Could not load theme settings from: %s" % self.confFile)
- logger.error(str(e))
+ nw.logException()
return False
## Main
@@ -333,9 +333,9 @@ class GuiTheme:
try:
with open(self.syntaxFile, mode="r", encoding="utf8") as inFile:
confParser.read_file(inFile)
- except Exception as e:
+ except Exception:
logger.error("Could not load syntax colours from: %s" % self.syntaxFile)
- logger.error(str(e))
+ nw.logException()
return False
## Main
@@ -637,9 +637,9 @@ class GuiIcons:
try:
with open(self.confFile, mode="r", encoding="utf8") as 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(str(e))
+ nw.logException()
return False
## Main
From d8cc091f9438199c2d26a24c4cbe3cb73148ea78 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Fri, 29 Jan 2021 18:39:48 +0100
Subject: [PATCH 5/5] Also use the new nwItem checkers in the NWItem class
---
nw/core/item.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/nw/core/item.py b/nw/core/item.py
index e2a5a4e8..62460e7e 100644
--- a/nw/core/item.py
+++ b/nw/core/item.py
@@ -28,8 +28,10 @@ import logging
from lxml import etree
-from nw.common import checkInt, isHandle
from nw.constants import nwItemType, nwItemClass, nwItemLayout
+from nw.common import (
+ checkInt, isHandle, isItemClass, isItemLayout, isItemType
+)
logger = logging.getLogger(__name__)
@@ -201,7 +203,7 @@ class NWItem():
"""
if isinstance(theType, nwItemType):
self.itemType = theType
- elif theType in nwItemType.__members__:
+ elif isItemType(theType):
self.itemType = nwItemType[theType]
else:
logger.error("Unrecognised item type '%s'" % theType)
@@ -214,7 +216,7 @@ class NWItem():
"""
if isinstance(theClass, nwItemClass):
self.itemClass = theClass
- elif theClass in nwItemClass.__members__:
+ elif isItemClass(theClass):
self.itemClass = nwItemClass[theClass]
else:
logger.error("Unrecognised item class '%s'" % theClass)
@@ -227,7 +229,7 @@ class NWItem():
"""
if isinstance(theLayout, nwItemLayout):
self.itemLayout = theLayout
- elif theLayout in nwItemLayout.__members__:
+ elif isItemLayout(theLayout):
self.itemLayout = nwItemLayout[theLayout]
else:
logger.error("Unrecognised item layout '%s'" % theLayout)