diff --git a/novelwriter/config.py b/novelwriter/config.py
index 760bb04b..e7891817 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -36,7 +36,7 @@ from PyQt5.QtCore import (
QTranslator
)
-from novelwriter.error import logException
+from novelwriter.error import logException, formatException
from novelwriter.common import splitVersionNumber, formatTimeStamp, NWConfigParser
from novelwriter.constants import nwFiles, nwUnicode
@@ -312,7 +312,7 @@ class Config:
logException()
self.hasError = True
self.errData.append("Could not create folder: %s" % self.confPath)
- self.errData.append(str(exc))
+ self.errData.append(formatException(exc))
self.confPath = None
# Check if config file exists
@@ -335,7 +335,7 @@ class Config:
logException()
self.hasError = True
self.errData.append("Could not create folder: %s" % self.dataPath)
- self.errData.append(str(exc))
+ self.errData.append(formatException(exc))
self.dataPath = None
# Host and Kernel
@@ -430,7 +430,7 @@ class Config:
logException()
self.hasError = True
self.errData.append("Could not load config file")
- self.errData.append(str(exc))
+ self.errData.append(formatException(exc))
return False
# Main
@@ -660,7 +660,7 @@ class Config:
logException()
self.hasError = True
self.errData.append("Could not save config file")
- self.errData.append(str(exc))
+ self.errData.append(formatException(exc))
return False
return True
@@ -691,7 +691,7 @@ class Config:
except Exception as exc:
self.hasError = True
self.errData.append("Could not load recent project cache")
- self.errData.append(str(exc))
+ self.errData.append(formatException(exc))
return False
return True
@@ -711,7 +711,7 @@ class Config:
except Exception as exc:
self.hasError = True
self.errData.append("Could not save recent project cache")
- self.errData.append(str(exc))
+ self.errData.append(formatException(exc))
return False
if os.path.isfile(cacheFile):
diff --git a/novelwriter/core/document.py b/novelwriter/core/document.py
index 3839e087..fe84a133 100644
--- a/novelwriter/core/document.py
+++ b/novelwriter/core/document.py
@@ -27,6 +27,7 @@ import os
import logging
from novelwriter.enum import nwItemLayout, nwItemClass
+from novelwriter.error import formatException
from novelwriter.common import isHandle, sha256sum
logger = logging.getLogger(__name__)
@@ -101,7 +102,7 @@ class NWDoc():
theText += inFile.read()
except Exception as exc:
- self._docError = str(exc)
+ self._docError = formatException(exc)
return None
else:
@@ -150,7 +151,7 @@ class NWDoc():
outFile.write(docMeta)
outFile.write(docText)
except Exception as exc:
- self._docError = str(exc)
+ self._docError = formatException(exc)
return False
# If we're here, the file was successfully saved, so we can
@@ -185,7 +186,7 @@ class NWDoc():
os.unlink(chkFile)
logger.debug("Deleted: %s", chkFile)
except Exception as exc:
- self._docError = str(exc)
+ self._docError = formatException(exc)
return False
return True
@@ -225,7 +226,7 @@ class NWDoc():
##
def _parseMeta(self, metaLine):
- """Parse a line from the document statting with the characters
+ """Parse a line from the document starting with the characters
%%~ that may contain meta data.
"""
if metaLine.startswith("%%~name:"):
diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py
index 77e75b0c..b4d94163 100644
--- a/novelwriter/core/index.py
+++ b/novelwriter/core/index.py
@@ -32,6 +32,7 @@ import novelwriter
from time import time
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
+from novelwriter.error import logException
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode
from novelwriter.core.document import NWDoc
from novelwriter.common import (
@@ -155,7 +156,7 @@ class NWIndex():
except Exception:
logger.error("Failed to load index file")
- novelwriter.logException()
+ logException()
self.indexBroken = True
return False
@@ -194,7 +195,7 @@ class NWIndex():
except Exception:
logger.error("Failed to save index file")
- novelwriter.logException()
+ logException()
return False
logger.verbose("Index saved in %.3f ms", (time() - tStart)*1000)
@@ -217,7 +218,7 @@ class NWIndex():
except Exception:
logger.error("Error while checking index")
- novelwriter.logException()
+ logException()
self.indexBroken = True
logger.verbose("Index check took %.3f ms", (time() - tStart)*1000)
diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py
index 4e6d705e..2cb8cde0 100644
--- a/novelwriter/core/options.py
+++ b/novelwriter/core/options.py
@@ -27,10 +27,10 @@ along with this program. If not, see .
import os
import json
import logging
-import novelwriter
-from novelwriter.constants import nwFiles
+from novelwriter.error import logException
from novelwriter.common import checkBool, checkFloat, checkInt, checkString
+from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -86,7 +86,7 @@ class OptionState():
theState = json.load(inFile)
except Exception:
logger.error("Failed to load GUI options file")
- novelwriter.logException()
+ logException()
return False
# Filter out unused variables
@@ -113,7 +113,7 @@ class OptionState():
json.dump(self._theState, outFile, indent=2)
except Exception:
logger.error("Failed to save GUI options file")
- novelwriter.logException()
+ logException()
return False
return True
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index a788afe6..c2481aab 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -37,14 +37,15 @@ from PyQt5.QtCore import QCoreApplication
from novelwriter.core.tree import NWTree
from novelwriter.core.item import NWItem
-from novelwriter.core.document import NWDoc
from novelwriter.core.status import NWStatus
from novelwriter.core.options import OptionState
+from novelwriter.core.document import NWDoc
+from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
+from novelwriter.error import logException
from novelwriter.common import (
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
makeFileNameSafe, hexToInt
)
-from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.constants import trConst, nwFiles, nwLabels
logger = logging.getLogger(__name__)
@@ -236,10 +237,13 @@ class NWProject():
return
- def newProject(self, projData={}):
+ def newProject(self, projData=None):
"""Create a new project by populating the project tree with a
few starter items.
"""
+ if projData is None:
+ projData = {}
+
popMinimal = projData.get("popMinimal", True)
popCustom = projData.get("popCustom", False)
popSample = projData.get("popSample", False)
@@ -1252,7 +1256,7 @@ class NWProject():
except Exception:
logger.error("Failed to project language file")
- novelwriter.logException()
+ logException()
return False
return True
@@ -1277,7 +1281,7 @@ class NWProject():
except Exception:
logger.error("Failed to read project lockfile")
- novelwriter.logException()
+ logException()
return ["ERROR"]
return theLines
@@ -1298,7 +1302,7 @@ class NWProject():
except Exception:
logger.error("Failed to write project lockfile")
- novelwriter.logException()
+ logException()
return False
return True
@@ -1315,7 +1319,7 @@ class NWProject():
os.unlink(lockFile)
except Exception:
logger.error("Failed to remove project lockfile")
- novelwriter.logException()
+ logException()
return False
return True
@@ -1488,7 +1492,7 @@ class NWProject():
except Exception:
logger.error("Failed to write session stats file")
- novelwriter.logException()
+ logException()
return False
return True
@@ -1526,7 +1530,7 @@ class NWProject():
except Exception:
errList.append(self.tr("Could not move: {0}").format(theFile))
logger.error("Could not move: %s", theFile)
- novelwriter.logException()
+ logException()
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
try:
@@ -1535,7 +1539,7 @@ class NWProject():
except Exception:
errList.append(self.tr("Could not delete: {0}").format(theFile))
logger.error("Could not delete: %s", theFile)
- novelwriter.logException()
+ logException()
else:
theErr = self._moveUnknownItem(theData, dataItem)
@@ -1549,7 +1553,7 @@ class NWProject():
except Exception:
errList.append(self.tr("Could not delete: {0}").format(theFolder))
logger.error("Could not delete: %s", theFolder)
- novelwriter.logException()
+ logException()
return errList
@@ -1569,7 +1573,7 @@ class NWProject():
logger.info("Moved to junk: %s", theSrc)
except Exception:
logger.error("Could not move item %s to junk", theSrc)
- novelwriter.logException()
+ logException()
return self.tr("Could not move item {0} to {1}.").format(theSrc, theJunk)
return ""
@@ -1604,7 +1608,7 @@ class NWProject():
os.unlink(rmFile)
except Exception:
logger.error("Could not delete: %s", rmFile)
- novelwriter.logException()
+ logException()
return False
return True
diff --git a/novelwriter/core/spellcheck.py b/novelwriter/core/spellcheck.py
index dcdbe089..3679c964 100644
--- a/novelwriter/core/spellcheck.py
+++ b/novelwriter/core/spellcheck.py
@@ -27,6 +27,8 @@ import os
import logging
import novelwriter
+from novelwriter.error import logException
+
logger = logging.getLogger(__name__)
@@ -107,7 +109,7 @@ class NWSpellEnchant():
self._projDict.add(newWord)
except Exception:
logger.error("Failed to add word to project word list %s", str(self._projectDict))
- novelwriter.logException()
+ logException()
return False
return True
@@ -135,7 +137,7 @@ class NWSpellEnchant():
spName = self._theDict.provider.name
except Exception:
logger.error("Failed to extract information about the dictionary")
- novelwriter.logException()
+ logException()
spTag = ""
spName = ""
@@ -169,7 +171,7 @@ class NWSpellEnchant():
except Exception:
logger.error("Failed to load project word list")
- novelwriter.logException()
+ logException()
return False
return True
diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py
index 04b754aa..0f3b2681 100644
--- a/novelwriter/core/tree.py
+++ b/novelwriter/core/tree.py
@@ -25,13 +25,13 @@ along with this program. If not, see .
import os
import logging
-import novelwriter
from time import time
from lxml import etree
from hashlib import sha256
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
+from novelwriter.error import logException
from novelwriter.common import checkHandle
from novelwriter.constants import nwConst, nwFiles
from novelwriter.core.item import NWItem
@@ -184,7 +184,7 @@ class NWTree():
except Exception:
logger.error("Could not write ToC file")
- novelwriter.logException()
+ logException()
return False
return True
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index 50db4f12..487f925e 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -34,6 +34,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.enum import nwAlert
+from novelwriter.error import logException
from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -161,7 +162,7 @@ class GuiWordList(QDialog):
except Exception:
logger.error("Could not save new word list")
- novelwriter.logException()
+ logException()
self.reject()
return False
diff --git a/novelwriter/error.py b/novelwriter/error.py
index 49b087e9..bde60599 100644
--- a/novelwriter/error.py
+++ b/novelwriter/error.py
@@ -43,7 +43,14 @@ def logException():
"""Log the content of an exception message.
"""
exType, exValue, _ = sys.exc_info()
- logger.error("%s: %s", exType.__name__, str(exValue).strip("'"))
+ logger.error("%s: %s", exType.__name__, str(exValue))
+
+
+def formatException(exc):
+ """Format an exception as a string the same way the default
+ exception handler does.
+ """
+ return f"{type(exc).__name__}: {str(exc)}"
# =============================================================================================== #
@@ -185,11 +192,11 @@ def exceptionHandler(exType, exValue, exTrace):
except Exception as exc:
logger.critical("Could not close the project before exiting")
- logger.critical(str(exc))
+ logger.critical(formatException(exc))
qApp.exit(1)
except Exception as exc:
- logger.critical(str(exc))
+ logger.critical(formatException(exc))
return
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index c5dc0924..5d826b74 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -41,6 +41,7 @@ from PyQt5.QtWidgets import (
from novelwriter.core import ToHtml
from novelwriter.enum import nwAlert, nwItemType, nwDocAction
+from novelwriter.error import logException
from novelwriter.constants import nwUnicode
logger = logging.getLogger(__name__)
@@ -185,7 +186,7 @@ class GuiDocViewer(QTextBrowser):
aDoc.doPostProcessing()
except Exception:
logger.error("Failed to generate preview for document with handle '%s'", tHandle)
- novelwriter.logException()
+ logException()
self.setText(self.tr("An error occurred while generating the preview."))
return False
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index be1f6754..87679691 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -37,6 +37,7 @@ from PyQt5.QtGui import (
)
from novelwriter.enum import nwItemLayout, nwItemType
+from novelwriter.error import logException
from novelwriter.common import NWConfigParser, readTextFile
from novelwriter.constants import nwLabels
@@ -250,7 +251,7 @@ class GuiTheme:
confParser.read_file(inFile)
except Exception:
logger.error("Could not load theme settings from: %s", self.themeFile)
- novelwriter.logException()
+ logException()
return False
# Main
@@ -310,7 +311,7 @@ class GuiTheme:
confParser.read_file(inFile)
except Exception:
logger.error("Could not load syntax colours from: %s", self.syntaxFile)
- novelwriter.logException()
+ logException()
return False
# Main
@@ -531,7 +532,7 @@ class GuiIcons:
confParser.read_file(inFile)
except Exception:
logger.error("Could not load icon theme settings from: %s", themeConf)
- novelwriter.logException()
+ logException()
return False
# Main
@@ -729,7 +730,7 @@ def _loadInternalName(confParser, confFile):
confParser.read_file(inFile)
except Exception:
logger.error("Could not load file: %s", confFile)
- novelwriter.logException()
+ logException()
return ""
return confParser.rdStr("Main", "name", "")
diff --git a/novelwriter/tools/build.py b/novelwriter/tools/build.py
index 9df60890..5863fc67 100644
--- a/novelwriter/tools/build.py
+++ b/novelwriter/tools/build.py
@@ -45,6 +45,7 @@ from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from novelwriter.core import ToHtml, ToOdt, ToMarkdown
from novelwriter.enum import nwAlert, nwItemType, nwItemLayout, nwItemClass
+from novelwriter.error import formatException, logException
from novelwriter.common import fuzzyTime, makeFileNameSafe
from novelwriter.constants import nwConst, nwFiles
from novelwriter.gui.custom import QSwitch
@@ -727,7 +728,7 @@ class GuiBuildNovel(QDialog):
except Exception:
logger.error("Failed to build document '%s'", tItem.itemHandle)
- novelwriter.logException()
+ logException()
if isPreview:
self.docView.setText((
"Failed to generate preview. "
@@ -873,7 +874,7 @@ class GuiBuildNovel(QDialog):
makeOdt.saveOpenDocText(savePath)
wSuccess = True
except Exception as exc:
- errMsg = str(exc)
+ errMsg = formatException(exc)
elif theFmt == self.FMT_FODT:
makeOdt = ToOdt(self.theProject, isFlat=True)
@@ -882,7 +883,7 @@ class GuiBuildNovel(QDialog):
makeOdt.saveFlatXML(savePath)
wSuccess = True
except Exception as exc:
- errMsg = str(exc)
+ errMsg = formatException(exc)
elif theFmt == self.FMT_HTM:
makeHtml = ToHtml(self.theProject)
@@ -894,7 +895,7 @@ class GuiBuildNovel(QDialog):
makeHtml.saveHTML5(savePath)
wSuccess = True
except Exception as exc:
- errMsg = str(exc)
+ errMsg = formatException(exc)
elif theFmt == self.FMT_NWD:
makeNwd = ToMarkdown(self.theProject)
@@ -907,7 +908,7 @@ class GuiBuildNovel(QDialog):
makeNwd.saveRawMarkdown(savePath)
wSuccess = True
except Exception as exc:
- errMsg = str(exc)
+ errMsg = formatException(exc)
elif theFmt in (self.FMT_MD, self.FMT_GH):
makeMd = ToMarkdown(self.theProject)
@@ -924,7 +925,7 @@ class GuiBuildNovel(QDialog):
makeMd.saveMarkdown(savePath)
wSuccess = True
except Exception as exc:
- errMsg = str(exc)
+ errMsg = formatException(exc)
elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M:
jsonData = {
@@ -969,7 +970,7 @@ class GuiBuildNovel(QDialog):
outFile.write(json.dumps(jsonData, indent=2))
wSuccess = True
except Exception as exc:
- errMsg = str(exc)
+ errMsg = formatException(exc)
elif theFmt == self.FMT_PDF:
try:
@@ -984,7 +985,7 @@ class GuiBuildNovel(QDialog):
wSuccess = True
except Exception as exc:
- errMsg = str(exc)
+ errMsg = formatException(exc)
else:
# If the if statements above and here match, it should not
@@ -1050,7 +1051,7 @@ class GuiBuildNovel(QDialog):
theData = json.loads(theJson)
except Exception:
logger.error("Failed to load build cache")
- novelwriter.logException()
+ logException()
return False
if "buildTime" in theData.keys():
@@ -1078,7 +1079,7 @@ class GuiBuildNovel(QDialog):
}, indent=2))
except Exception:
logger.error("Failed to save build cache")
- novelwriter.logException()
+ logException()
return False
return True
@@ -1302,9 +1303,12 @@ class GuiBuildNovelDocView(QTextBrowser):
return
- def setStyleSheet(self, theStyles=[]):
+ def setStyleSheet(self, theStyles=None):
"""Set the stylesheet for the preview document.
"""
+ if theStyles is None:
+ theStyles = []
+
if not theStyles:
theStyles.append("h1, h2 {color: rgb(66, 113, 174);}")
theStyles.append("h3, h4 {color: rgb(50, 50, 50);}")
diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py
index fb3930a1..9ffa800f 100644
--- a/novelwriter/tools/writingstats.py
+++ b/novelwriter/tools/writingstats.py
@@ -38,6 +38,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.enum import nwAlert
+from novelwriter.error import formatException
from novelwriter.common import formatTime, checkInt, checkIntRange, checkIntTuple
from novelwriter.constants import nwConst, nwFiles
from novelwriter.gui.custom import QSwitch
@@ -404,7 +405,7 @@ class GuiWritingStats(QDialog):
wSuccess = True
except Exception as exc:
- errMsg = str(exc)
+ errMsg = formatException(exc)
wSuccess = False
# Report to user
@@ -480,9 +481,9 @@ class GuiWritingStats(QDialog):
self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle))
except Exception as exc:
- self.theParent.makeAlert([
- self.tr("Failed to read session log file."), str(exc)
- ], nwAlert.ERROR)
+ self.theParent.makeAlert(self.tr(
+ "Failed to read session log file."
+ ), nwAlert.ERROR, exception=exc)
return False
ttWords = ttNovel + ttNotes
diff --git a/tests/mock.py b/tests/mock.py
index 126ebb02..6872c6c3 100644
--- a/tests/mock.py
+++ b/tests/mock.py
@@ -113,8 +113,8 @@ class MockApp:
# =========================================================================== #
def causeOSError(*args, **kwargs):
- raise OSError("OSError")
+ raise OSError("Mock OSError")
def causeException(*args, **kwargs):
- raise Exception("Exception")
+ raise Exception("Mock Exception")
diff --git a/tests/test_core/test_core_document.py b/tests/test_core/test_core_document.py
index dd72b654..16a6ab0f 100644
--- a/tests/test_core/test_core_document.py
+++ b/tests/test_core/test_core_document.py
@@ -56,7 +56,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
mp.setattr("builtins.open", causeOSError)
theDoc = NWDoc(theProject, sHandle)
assert theDoc.readDocument() is None
- assert theDoc.getError() == "OSError"
+ assert theDoc.getError() == "OSError: Mock OSError"
# Load the text
theDoc = NWDoc(theProject, sHandle)
@@ -107,7 +107,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert theDoc.writeDocument(theText) is False
- assert theDoc.getError() == "OSError"
+ assert theDoc.getError() == "OSError: Mock OSError"
# Saving with no handle
theDoc._docHandle = None
@@ -126,7 +126,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
mp.setattr("os.unlink", causeOSError)
theDoc = NWDoc(theProject, xHandle)
assert theDoc.deleteDocument() is False
- assert theDoc.getError() == "OSError"
+ assert theDoc.getError() == "OSError: Mock OSError"
# Make the delete pass
theDoc = NWDoc(theProject, xHandle)