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)