diff --git a/docs/source/conf.py b/docs/source/conf.py
index e7446bba..b253da66 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -28,7 +28,6 @@ version = "1.5"
# The full version, including alpha/beta/rc tags
release = "1.5-alpha0"
-
# -- General configuration ---------------------------------------------------
os.environ["TZ"] = "Europe/Oslo"
diff --git a/nw/common.py b/nw/common.py
index daa2b32d..c705922c 100644
--- a/nw/common.py
+++ b/nw/common.py
@@ -26,24 +26,27 @@ along with this program. If not, see .
import logging
from datetime import datetime
+from configparser import ConfigParser
from PyQt5.QtWidgets import qApp
from PyQt5.QtCore import QCoreApplication
from nw.enum import nwItemClass, nwItemType, nwItemLayout
+from nw.error import logException
from nw.constants import nwConst, nwUnicode
logger = logging.getLogger(__name__)
+# =============================================================================================== #
+# Checker Functions
+# =============================================================================================== #
+
def checkString(value, default, allowNone=False):
"""Check if a variable is a string or a none.
"""
- if allowNone:
- if value is None:
- return None
- if value == "None":
- return None
+ if allowNone and (value is None or value == "None"):
+ return None
if isinstance(value, str):
return str(value)
return default
@@ -52,11 +55,8 @@ def checkString(value, default, allowNone=False):
def checkInt(value, default, allowNone=False):
"""Check if a variable is an integer or a none.
"""
- if allowNone:
- if value is None:
- return None
- if value == "None":
- return None
+ if allowNone and (value is None or value == "None"):
+ return None
try:
return int(value)
except Exception:
@@ -66,11 +66,9 @@ def checkInt(value, default, allowNone=False):
def checkBool(value, default, allowNone=False):
"""Check if a variable is a boolean or a none.
"""
- if allowNone:
- if value is None:
- return None
- if value == "None":
- return None
+ if allowNone and (value is None or value == "None"):
+ return None
+
if isinstance(value, str):
if value == "True":
return True
@@ -78,6 +76,7 @@ def checkBool(value, default, allowNone=False):
return False
else:
return default
+
elif isinstance(value, int):
if value == 1:
return True
@@ -85,67 +84,69 @@ def checkBool(value, default, allowNone=False):
return False
else:
return default
+
return default
def checkHandle(value, default, allowNone=False):
"""Check if a value is a handle.
"""
- if allowNone:
- if value is None:
- return None
- if value == "None":
- return None
+ if allowNone and (value is None or value == "None"):
+ return None
if isHandle(value):
return str(value)
return default
-def isHandle(theString):
+# =============================================================================================== #
+# Validator Functions
+# =============================================================================================== #
+
+def isHandle(value):
"""Check if a string is a valid novelWriter handle.
Note: This is case sensitive. Must be lower case!
"""
- if not isinstance(theString, str):
+ if not isinstance(value, str):
return False
- if len(theString) != 13:
+ if len(value) != 13:
return False
- for c in theString:
+ for c in value:
if c not in "0123456789abcdef":
return False
return True
-def isTitleTag(theString):
+def isTitleTag(value):
"""Check if a string is a valid title string.
"""
- if not isinstance(theString, str):
+ if not isinstance(value, str):
return False
- if len(theString) != 7:
+ if len(value) != 7:
return False
- if not theString.startswith("T"):
+ if not value.startswith("T"):
return False
- for c in theString[1:]:
+ for c in value[1:]:
if c not in "0123456789":
return False
return True
-def isItemClass(theString):
- """Check if an item is a calid nwItemClass identifier.
+def isItemClass(value):
+ """Check if a string is a valid nwItemClass identifier.
"""
- return theString in nwItemClass.__members__
+ return value in nwItemClass.__members__
-def isItemType(theString):
- """Check if an item is a calid nwItemType identifier.
+def isItemType(value):
+ """Check if a string is a valid nwItemType identifier.
"""
- return theString in nwItemType.__members__
+ return value in nwItemType.__members__
-def isItemLayout(theString):
- """Check if an item is a calid nwItemLayout identifier.
+def isItemLayout(value):
+ """Check if a string is a valid nwItemLayout identifier.
"""
- return theString in nwItemLayout.__members__
+ return value in nwItemLayout.__members__
def hexToInt(value, default=0):
@@ -159,14 +160,19 @@ def hexToInt(value, default=0):
return default
-def formatInt(theInt):
+# =============================================================================================== #
+# Formatting Functions
+# =============================================================================================== #
+
+def formatInt(value):
"""Formats an integer with k, M, G etc.
"""
- postFix = ["k", "M", "G", "T", "P", "E"]
- theVal = float(theInt)
+ if not isinstance(value, int):
+ return "ERR"
+ theVal = float(value)
if theVal > 1000.0:
- for pF in postFix:
+ for pF in ["k", "M", "G", "T", "P", "E"]:
theVal /= 1000.0
if theVal < 1000.0:
if theVal < 10.0:
@@ -176,7 +182,7 @@ def formatInt(theInt):
else:
return f"{theVal:3.0f}{nwUnicode.U_THSP}{pF}"
- return str(theInt)
+ return str(value)
def formatTimeStamp(theTime, fileSafe=False):
@@ -201,6 +207,23 @@ def formatTime(tS):
return "ERROR"
+def parseTimeStamp(theStamp, default, allowNone=False):
+ """Parses a text representation of a time stamp and converts it into
+ a float. Note that negative timestamps cause an OSError on Windows.
+ See https://bugs.python.org/issue29097
+ """
+ if str(theStamp).lower() == "none" and allowNone:
+ return None
+ try:
+ return datetime.strptime(theStamp, nwConst.FMT_TSTAMP).timestamp()
+ except Exception:
+ return default
+
+
+# =============================================================================================== #
+# String Functions
+# =============================================================================================== #
+
def splitVersionNumber(vString):
""" Splits a version string on the form aa.bb.cc into major, minor
and patch, and computes an integer value aabbcc.
@@ -308,27 +331,8 @@ def fuzzyTime(secDiff):
).format(int(round(secDiff/31557600)))
-def makeFileNameSafe(theText):
- """Returns a filename safe version of the text.
- """
- cleanName = ""
- for c in theText.strip():
- if c.isalpha() or c.isdigit() or c == " ":
- cleanName += c
- return cleanName
-
-
-def getGuiItem(theName):
- """Returns a QtWidget based on its objectName.
- """
- for qWidget in qApp.topLevelWidgets():
- if qWidget.objectName() == theName:
- return qWidget
- return None
-
-
def numberToRoman(numVal, isLower=False):
- """Convert an integer to a roman number.
+ """Convert an integer to a Roman number.
"""
if not isinstance(numVal, int):
return "NAN"
@@ -349,3 +353,110 @@ def numberToRoman(numVal, isLower=False):
break
return romNum.lower() if isLower else romNum
+
+
+# =============================================================================================== #
+# Other Functions
+# =============================================================================================== #
+
+def makeFileNameSafe(theText):
+ """Returns a filename safe version of the text.
+ """
+ cleanName = ""
+ for c in theText.strip():
+ if c.isalpha() or c.isdigit() or c == " ":
+ cleanName += c
+ return cleanName
+
+
+def getGuiItem(theName):
+ """Returns a QtWidget based on its objectName.
+ """
+ for qWidget in qApp.topLevelWidgets():
+ if qWidget.objectName() == theName:
+ return qWidget
+ return None
+
+
+# =============================================================================================== #
+# Classes
+# =============================================================================================== #
+
+class NWConfigParser(ConfigParser):
+
+ CNF_STR = 0
+ CNF_INT = 1
+ CNF_BOOL = 2
+ CNF_S_LST = 3
+ CNF_I_LST = 4
+
+ def __init__(self):
+ super().__init__()
+
+ def rdStr(self, section, option, default):
+ """Read string value.
+ """
+ return self._parseLine(section, option, default, self.CNF_STR)
+
+ def rdInt(self, section, option, default):
+ """Read integer value.
+ """
+ return self._parseLine(section, option, default, self.CNF_INT)
+
+ def rdBool(self, section, option, default):
+ """Read boolean value.
+ """
+ return self._parseLine(section, option, default, self.CNF_BOOL)
+
+ def rdStrList(self, section, option, default):
+ """Read string list.
+ """
+ return self._parseLine(section, option, default, self.CNF_S_LST)
+
+ def rdIntList(self, section, option, default):
+ """Read integer list.
+ """
+ return self._parseLine(section, option, default, self.CNF_I_LST)
+
+ ##
+ # Internal Functions
+ ##
+
+ def _unpackList(self, value, default, type):
+ """Unpack a comma-separated string of items into a list.
+ """
+ inList = value.split(",")
+ outList = []
+ if isinstance(default, list):
+ outList = default.copy()
+ for i in range(min(len(inList), len(outList))):
+ try:
+ if type == self.CNF_S_LST:
+ outList[i] = inList[i].strip()
+ elif type == self.CNF_I_LST:
+ outList[i] = int(inList[i].strip())
+ except Exception:
+ continue
+ return outList
+
+ def _parseLine(self, section, option, default, type):
+ """Parse a line and return the correct datatype.
+ """
+ if self.has_option(section, option):
+ try:
+ if type == self.CNF_STR:
+ return self.get(section, option)
+ elif type == self.CNF_INT:
+ return self.getint(section, option)
+ elif type == self.CNF_BOOL:
+ return self.getboolean(section, option)
+ elif type in (self.CNF_I_LST, self.CNF_S_LST):
+ return self._unpackList(self.get(section, option), default, type)
+ except ValueError:
+ logger.error("Could not read '%s':'%s' from config", str(section), str(option))
+ logException()
+ return default
+
+ return default
+
+# END Class NWConfigParser
diff --git a/nw/config.py b/nw/config.py
index faabe6ae..b4b8d1b2 100644
--- a/nw/config.py
+++ b/nw/config.py
@@ -28,7 +28,6 @@ import sys
import json
import shutil
import logging
-import configparser
from time import time
@@ -39,7 +38,7 @@ from PyQt5.QtCore import (
)
from nw.error import logException
-from nw.common import splitVersionNumber, formatTimeStamp
+from nw.common import splitVersionNumber, formatTimeStamp, NWConfigParser
from nw.constants import nwConst, nwFiles, nwUnicode
logger = logging.getLogger(__name__)
@@ -47,12 +46,6 @@ logger = logging.getLogger(__name__)
class Config:
- CNF_STR = 0
- CNF_INT = 1
- CNF_BOOL = 2
- CNF_S_LST = 3
- CNF_I_LST = 4
-
LANG_NW = 1
LANG_PROJ = 2
@@ -432,11 +425,11 @@ class Config:
if self.confPath is None:
return False
- cnfParse = configparser.ConfigParser()
+ theConf = NWConfigParser()
cnfPath = os.path.join(self.confPath, self.confFile)
try:
with open(cnfPath, mode="r", encoding="utf-8") as inFile:
- cnfParse.read_file(inFile)
+ theConf.read_file(inFile)
except Exception as e:
logger.error("Could not load config file")
logException()
@@ -447,237 +440,95 @@ class Config:
# Main
cnfSec = "Main"
- self.guiTheme = self._parseLine(
- cnfParse, cnfSec, "theme", self.CNF_STR, self.guiTheme
- )
- self.guiSyntax = self._parseLine(
- cnfParse, cnfSec, "syntax", self.CNF_STR, self.guiSyntax
- )
- self.guiIcons = self._parseLine(
- cnfParse, cnfSec, "icons", self.CNF_STR, self.guiIcons
- )
- self.guiDark = self._parseLine(
- cnfParse, cnfSec, "guidark", self.CNF_BOOL, self.guiDark
- )
- self.guiFont = self._parseLine(
- cnfParse, cnfSec, "guifont", self.CNF_STR, self.guiFont
- )
- self.guiFontSize = self._parseLine(
- cnfParse, cnfSec, "guifontsize", self.CNF_INT, self.guiFontSize
- )
- self.lastNotes = self._parseLine(
- cnfParse, cnfSec, "lastnotes", self.CNF_STR, self.lastNotes
- )
- self.guiLang = self._parseLine(
- cnfParse, cnfSec, "guilang", self.CNF_STR, self.guiLang
- )
+ self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme)
+ self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax)
+ self.guiIcons = theConf.rdStr(cnfSec, "icons", self.guiIcons)
+ self.guiDark = theConf.rdBool(cnfSec, "guidark", self.guiDark)
+ self.guiFont = theConf.rdStr(cnfSec, "guifont", self.guiFont)
+ self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize", self.guiFontSize)
+ self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes)
+ self.guiLang = theConf.rdStr(cnfSec, "guilang", self.guiLang)
# Sizes
cnfSec = "Sizes"
- self.winGeometry = self._parseLine(
- cnfParse, cnfSec, "geometry", self.CNF_I_LST, self.winGeometry
- )
- self.prefGeometry = self._parseLine(
- cnfParse, cnfSec, "preferences", self.CNF_I_LST, self.prefGeometry
- )
- self.treeColWidth = self._parseLine(
- cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth
- )
- self.novelColWidth = self._parseLine(
- cnfParse, cnfSec, "novelcols", self.CNF_I_LST, self.novelColWidth
- )
- self.projColWidth = self._parseLine(
- cnfParse, cnfSec, "projcols", self.CNF_I_LST, self.projColWidth
- )
- self.mainPanePos = self._parseLine(
- cnfParse, cnfSec, "mainpane", self.CNF_I_LST, self.mainPanePos
- )
- self.docPanePos = self._parseLine(
- cnfParse, cnfSec, "docpane", self.CNF_I_LST, self.docPanePos
- )
- self.viewPanePos = self._parseLine(
- cnfParse, cnfSec, "viewpane", self.CNF_I_LST, self.viewPanePos
- )
- self.outlnPanePos = self._parseLine(
- cnfParse, cnfSec, "outlinepane", self.CNF_I_LST, self.outlnPanePos
- )
- self.isFullScreen = self._parseLine(
- cnfParse, cnfSec, "fullscreen", self.CNF_BOOL, self.isFullScreen
- )
- self.hideVScroll = self._parseLine(
- cnfParse, cnfSec, "hidevscroll", self.CNF_BOOL, self.hideVScroll
- )
- self.hideHScroll = self._parseLine(
- cnfParse, cnfSec, "hidehscroll", self.CNF_BOOL, self.hideHScroll
- )
+ self.winGeometry = theConf.rdIntList(cnfSec, "geometry", self.winGeometry)
+ self.prefGeometry = theConf.rdIntList(cnfSec, "preferences", self.prefGeometry)
+ self.treeColWidth = theConf.rdIntList(cnfSec, "treecols", self.treeColWidth)
+ self.novelColWidth = theConf.rdIntList(cnfSec, "novelcols", self.novelColWidth)
+ self.projColWidth = theConf.rdIntList(cnfSec, "projcols", self.projColWidth)
+ self.mainPanePos = theConf.rdIntList(cnfSec, "mainpane", self.mainPanePos)
+ self.docPanePos = theConf.rdIntList(cnfSec, "docpane", self.docPanePos)
+ self.viewPanePos = theConf.rdIntList(cnfSec, "viewpane", self.viewPanePos)
+ self.outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self.outlnPanePos)
+ self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen", self.isFullScreen)
+ self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll", self.hideVScroll)
+ self.hideHScroll = theConf.rdBool(cnfSec, "hidehscroll", self.hideHScroll)
# Project
cnfSec = "Project"
- self.autoSaveProj = self._parseLine(
- cnfParse, cnfSec, "autosaveproject", self.CNF_INT, self.autoSaveProj
- )
- self.autoSaveDoc = self._parseLine(
- cnfParse, cnfSec, "autosavedoc", self.CNF_INT, self.autoSaveDoc
- )
+ self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj)
+ self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc)
# Editor
cnfSec = "Editor"
- self.textFont = self._parseLine(
- cnfParse, cnfSec, "textfont", self.CNF_STR, self.textFont
- )
- self.textSize = self._parseLine(
- cnfParse, cnfSec, "textsize", self.CNF_INT, self.textSize
- )
- self.textFixedW = self._parseLine(
- cnfParse, cnfSec, "fixedwidth", self.CNF_BOOL, self.textFixedW
- )
- self.textWidth = self._parseLine(
- cnfParse, cnfSec, "width", self.CNF_INT, self.textWidth
- )
- self.textMargin = self._parseLine(
- cnfParse, cnfSec, "margin", self.CNF_INT, self.textMargin
- )
- self.tabWidth = self._parseLine(
- cnfParse, cnfSec, "tabwidth", self.CNF_INT, self.tabWidth
- )
- self.focusWidth = self._parseLine(
- cnfParse, cnfSec, "focuswidth", self.CNF_INT, self.focusWidth
- )
- self.hideFocusFooter = self._parseLine(
- cnfParse, cnfSec, "hidefocusfooter", self.CNF_BOOL, self.hideFocusFooter
- )
- self.doJustify = self._parseLine(
- cnfParse, cnfSec, "justify", self.CNF_BOOL, self.doJustify
- )
- self.autoSelect = self._parseLine(
- cnfParse, cnfSec, "autoselect", self.CNF_BOOL, self.autoSelect
- )
- self.doReplace = self._parseLine(
- cnfParse, cnfSec, "autoreplace", self.CNF_BOOL, self.doReplace
- )
- self.doReplaceSQuote = self._parseLine(
- cnfParse, cnfSec, "repsquotes", self.CNF_BOOL, self.doReplaceSQuote
- )
- self.doReplaceDQuote = self._parseLine(
- cnfParse, cnfSec, "repdquotes", self.CNF_BOOL, self.doReplaceDQuote
- )
- self.doReplaceDash = self._parseLine(
- cnfParse, cnfSec, "repdash", self.CNF_BOOL, self.doReplaceDash
- )
- self.doReplaceDots = self._parseLine(
- cnfParse, cnfSec, "repdots", self.CNF_BOOL, self.doReplaceDots
- )
- self.scrollPastEnd = self._parseLine(
- cnfParse, cnfSec, "scrollpastend", self.CNF_BOOL, self.scrollPastEnd
- )
- self.autoScroll = self._parseLine(
- cnfParse, cnfSec, "autoscroll", self.CNF_BOOL, self.autoScroll
- )
- self.autoScrollPos = self._parseLine(
- cnfParse, cnfSec, "autoscrollpos", self.CNF_INT, self.autoScrollPos
- )
- self.fmtSingleQuotes = self._parseLine(
- cnfParse, cnfSec, "fmtsinglequote", self.CNF_S_LST, self.fmtSingleQuotes
- )
- self.fmtDoubleQuotes = self._parseLine(
- cnfParse, cnfSec, "fmtdoublequote", self.CNF_S_LST, self.fmtDoubleQuotes
- )
- self.fmtPadBefore = self._parseLine(
- cnfParse, cnfSec, "fmtpadbefore", self.CNF_STR, self.fmtPadBefore
- )
- self.fmtPadAfter = self._parseLine(
- cnfParse, cnfSec, "fmtpadafter", self.CNF_STR, self.fmtPadAfter
- )
- self.fmtPadThin = self._parseLine(
- cnfParse, cnfSec, "fmtpadthin", self.CNF_BOOL, self.fmtPadThin
- )
- self.spellTool = self._parseLine(
- cnfParse, cnfSec, "spelltool", self.CNF_STR, self.spellTool
- )
- self.spellLanguage = self._parseLine(
- cnfParse, cnfSec, "spellcheck", self.CNF_STR, self.spellLanguage
- )
- self.showTabsNSpaces = self._parseLine(
- cnfParse, cnfSec, "showtabsnspaces", self.CNF_BOOL, self.showTabsNSpaces
- )
- self.showLineEndings = self._parseLine(
- cnfParse, cnfSec, "showlineendings", self.CNF_BOOL, self.showLineEndings
- )
- self.showMultiSpaces = self._parseLine(
- cnfParse, cnfSec, "showmultispaces", self.CNF_BOOL, self.showMultiSpaces
- )
- self.bigDocLimit = self._parseLine(
- cnfParse, cnfSec, "bigdoclimit", self.CNF_INT, self.bigDocLimit
- )
- self.showFullPath = self._parseLine(
- cnfParse, cnfSec, "showfullpath", self.CNF_BOOL, self.showFullPath
- )
- self.highlightQuotes = self._parseLine(
- cnfParse, cnfSec, "highlightquotes", self.CNF_BOOL, self.highlightQuotes
- )
- self.allowOpenSQuote = self._parseLine(
- cnfParse, cnfSec, "allowopensquote", self.CNF_BOOL, self.allowOpenSQuote
- )
- self.allowOpenDQuote = self._parseLine(
- cnfParse, cnfSec, "allowopendquote", self.CNF_BOOL, self.allowOpenDQuote
- )
- self.highlightEmph = self._parseLine(
- cnfParse, cnfSec, "highlightemph", self.CNF_BOOL, self.highlightEmph
- )
- self.stopWhenIdle = self._parseLine(
- cnfParse, cnfSec, "stopwhenidle", self.CNF_BOOL, self.stopWhenIdle
- )
- self.userIdleTime = self._parseLine(
- cnfParse, cnfSec, "useridletime", self.CNF_INT, self.userIdleTime
- )
+ self.textFont = theConf.rdStr(cnfSec, "textfont", self.textFont)
+ self.textSize = theConf.rdInt(cnfSec, "textsize", self.textSize)
+ self.textFixedW = theConf.rdBool(cnfSec, "fixedwidth", self.textFixedW)
+ self.textWidth = theConf.rdInt(cnfSec, "width", self.textWidth)
+ self.textMargin = theConf.rdInt(cnfSec, "margin", self.textMargin)
+ self.tabWidth = theConf.rdInt(cnfSec, "tabwidth", self.tabWidth)
+ self.focusWidth = theConf.rdInt(cnfSec, "focuswidth", self.focusWidth)
+ self.hideFocusFooter = theConf.rdBool(cnfSec, "hidefocusfooter", self.hideFocusFooter)
+ self.doJustify = theConf.rdBool(cnfSec, "justify", self.doJustify)
+ self.autoSelect = theConf.rdBool(cnfSec, "autoselect", self.autoSelect)
+ self.doReplace = theConf.rdBool(cnfSec, "autoreplace", self.doReplace)
+ self.doReplaceSQuote = theConf.rdBool(cnfSec, "repsquotes", self.doReplaceSQuote)
+ self.doReplaceDQuote = theConf.rdBool(cnfSec, "repdquotes", self.doReplaceDQuote)
+ self.doReplaceDash = theConf.rdBool(cnfSec, "repdash", self.doReplaceDash)
+ self.doReplaceDots = theConf.rdBool(cnfSec, "repdots", self.doReplaceDots)
+ self.scrollPastEnd = theConf.rdBool(cnfSec, "scrollpastend", self.scrollPastEnd)
+ self.autoScroll = theConf.rdBool(cnfSec, "autoscroll", self.autoScroll)
+ self.autoScrollPos = theConf.rdInt(cnfSec, "autoscrollpos", self.autoScrollPos)
+ self.fmtSingleQuotes = theConf.rdStrList(cnfSec, "fmtsinglequote", self.fmtSingleQuotes)
+ self.fmtDoubleQuotes = theConf.rdStrList(cnfSec, "fmtdoublequote", self.fmtDoubleQuotes)
+ self.fmtPadBefore = theConf.rdStr(cnfSec, "fmtpadbefore", self.fmtPadBefore)
+ self.fmtPadAfter = theConf.rdStr(cnfSec, "fmtpadafter", self.fmtPadAfter)
+ self.fmtPadThin = theConf.rdBool(cnfSec, "fmtpadthin", self.fmtPadThin)
+ self.spellTool = theConf.rdStr(cnfSec, "spelltool", self.spellTool)
+ self.spellLanguage = theConf.rdStr(cnfSec, "spellcheck", self.spellLanguage)
+ self.showTabsNSpaces = theConf.rdBool(cnfSec, "showtabsnspaces", self.showTabsNSpaces)
+ self.showLineEndings = theConf.rdBool(cnfSec, "showlineendings", self.showLineEndings)
+ self.showMultiSpaces = theConf.rdBool(cnfSec, "showmultispaces", self.showMultiSpaces)
+ self.bigDocLimit = theConf.rdInt(cnfSec, "bigdoclimit", self.bigDocLimit)
+ self.showFullPath = theConf.rdBool(cnfSec, "showfullpath", self.showFullPath)
+ self.highlightQuotes = theConf.rdBool(cnfSec, "highlightquotes", self.highlightQuotes)
+ self.allowOpenSQuote = theConf.rdBool(cnfSec, "allowopensquote", self.allowOpenSQuote)
+ self.allowOpenDQuote = theConf.rdBool(cnfSec, "allowopendquote", self.allowOpenDQuote)
+ self.highlightEmph = theConf.rdBool(cnfSec, "highlightemph", self.highlightEmph)
+ self.stopWhenIdle = theConf.rdBool(cnfSec, "stopwhenidle", self.stopWhenIdle)
+ self.userIdleTime = theConf.rdInt(cnfSec, "useridletime", self.userIdleTime)
# Backup
cnfSec = "Backup"
- self.backupPath = self._parseLine(
- cnfParse, cnfSec, "backuppath", self.CNF_STR, self.backupPath
- )
- self.backupOnClose = self._parseLine(
- cnfParse, cnfSec, "backuponclose", self.CNF_BOOL, self.backupOnClose
- )
- self.askBeforeBackup = self._parseLine(
- cnfParse, cnfSec, "askbeforebackup", self.CNF_BOOL, self.askBeforeBackup
- )
+ self.backupPath = theConf.rdStr(cnfSec, "backuppath", self.backupPath)
+ self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose", self.backupOnClose)
+ self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup", self.askBeforeBackup)
# State
cnfSec = "State"
- self.showRefPanel = self._parseLine(
- cnfParse, cnfSec, "showrefpanel", self.CNF_BOOL, self.showRefPanel
- )
- self.viewComments = self._parseLine(
- cnfParse, cnfSec, "viewcomments", self.CNF_BOOL, self.viewComments
- )
- self.viewSynopsis = self._parseLine(
- cnfParse, cnfSec, "viewsynopsis", self.CNF_BOOL, self.viewSynopsis
- )
- self.searchCase = self._parseLine(
- cnfParse, cnfSec, "searchcase", self.CNF_BOOL, self.searchCase
- )
- self.searchWord = self._parseLine(
- cnfParse, cnfSec, "searchword", self.CNF_BOOL, self.searchWord
- )
- self.searchRegEx = self._parseLine(
- cnfParse, cnfSec, "searchregex", self.CNF_BOOL, self.searchRegEx
- )
- self.searchLoop = self._parseLine(
- cnfParse, cnfSec, "searchloop", self.CNF_BOOL, self.searchLoop
- )
- self.searchNextFile = self._parseLine(
- cnfParse, cnfSec, "searchnextfile", self.CNF_BOOL, self.searchNextFile
- )
- self.searchMatchCap = self._parseLine(
- cnfParse, cnfSec, "searchmatchcap", self.CNF_BOOL, self.searchMatchCap
- )
+ self.showRefPanel = theConf.rdBool(cnfSec, "showrefpanel", self.showRefPanel)
+ self.viewComments = theConf.rdBool(cnfSec, "viewcomments", self.viewComments)
+ self.viewSynopsis = theConf.rdBool(cnfSec, "viewsynopsis", self.viewSynopsis)
+ self.searchCase = theConf.rdBool(cnfSec, "searchcase", self.searchCase)
+ self.searchWord = theConf.rdBool(cnfSec, "searchword", self.searchWord)
+ self.searchRegEx = theConf.rdBool(cnfSec, "searchregex", self.searchRegEx)
+ self.searchLoop = theConf.rdBool(cnfSec, "searchloop", self.searchLoop)
+ self.searchNextFile = theConf.rdBool(cnfSec, "searchnextfile", self.searchNextFile)
+ self.searchMatchCap = theConf.rdBool(cnfSec, "searchmatchcap", self.searchMatchCap)
# Path
cnfSec = "Path"
- self.lastPath = self._parseLine(
- cnfParse, cnfSec, "lastpath", self.CNF_STR, self.lastPath
- )
+ self.lastPath = theConf.rdStr(cnfSec, "lastpath", self.lastPath)
# Check Certain Values for None
self.spellLanguage = self._checkNone(self.spellLanguage)
@@ -700,115 +551,106 @@ class Config:
if self.confPath is None:
return False
- cnfParse = configparser.ConfigParser()
+ theConf = NWConfigParser()
- # Set options
+ theConf["Main"] = {
+ "timestamp": formatTimeStamp(time()),
+ "theme": str(self.guiTheme),
+ "syntax": str(self.guiSyntax),
+ "icons": str(self.guiIcons),
+ "guidark": str(self.guiDark),
+ "guifont": str(self.guiFont),
+ "guifontsize": str(self.guiFontSize),
+ "lastnotes": str(self.lastNotes),
+ "guilang": str(self.guiLang),
+ }
- # Main
- cnfSec = "Main"
- cnfParse.add_section(cnfSec)
- cnfParse.set(cnfSec, "timestamp", formatTimeStamp(time()))
- cnfParse.set(cnfSec, "theme", str(self.guiTheme))
- cnfParse.set(cnfSec, "syntax", str(self.guiSyntax))
- cnfParse.set(cnfSec, "icons", str(self.guiIcons))
- cnfParse.set(cnfSec, "guidark", str(self.guiDark))
- cnfParse.set(cnfSec, "guifont", str(self.guiFont))
- cnfParse.set(cnfSec, "guifontsize", str(self.guiFontSize))
- cnfParse.set(cnfSec, "lastnotes", str(self.lastNotes))
- cnfParse.set(cnfSec, "guilang", str(self.guiLang))
+ theConf["Sizes"] = {
+ "geometry": self._packList(self.winGeometry),
+ "preferences": self._packList(self.prefGeometry),
+ "treecols": self._packList(self.treeColWidth),
+ "novelcols": self._packList(self.novelColWidth),
+ "projcols": self._packList(self.projColWidth),
+ "mainpane": self._packList(self.mainPanePos),
+ "docpane": self._packList(self.docPanePos),
+ "viewpane": self._packList(self.viewPanePos),
+ "outlinepane": self._packList(self.outlnPanePos),
+ "fullscreen": str(self.isFullScreen),
+ "hidevscroll": str(self.hideVScroll),
+ "hidehscroll": str(self.hideHScroll),
+ }
- # Sizes
- cnfSec = "Sizes"
- cnfParse.add_section(cnfSec)
- cnfParse.set(cnfSec, "geometry", self._packList(self.winGeometry))
- cnfParse.set(cnfSec, "preferences", self._packList(self.prefGeometry))
- cnfParse.set(cnfSec, "treecols", self._packList(self.treeColWidth))
- cnfParse.set(cnfSec, "novelcols", self._packList(self.novelColWidth))
- cnfParse.set(cnfSec, "projcols", self._packList(self.projColWidth))
- cnfParse.set(cnfSec, "mainpane", self._packList(self.mainPanePos))
- cnfParse.set(cnfSec, "docpane", self._packList(self.docPanePos))
- cnfParse.set(cnfSec, "viewpane", self._packList(self.viewPanePos))
- cnfParse.set(cnfSec, "outlinepane", self._packList(self.outlnPanePos))
- cnfParse.set(cnfSec, "fullscreen", str(self.isFullScreen))
- cnfParse.set(cnfSec, "hidevscroll", str(self.hideVScroll))
- cnfParse.set(cnfSec, "hidehscroll", str(self.hideHScroll))
+ theConf["Project"] = {
+ "autosaveproject": str(self.autoSaveProj),
+ "autosavedoc": str(self.autoSaveDoc),
+ }
- # Project
- cnfSec = "Project"
- cnfParse.add_section(cnfSec)
- cnfParse.set(cnfSec, "autosaveproject", str(self.autoSaveProj))
- cnfParse.set(cnfSec, "autosavedoc", str(self.autoSaveDoc))
+ theConf["Editor"] = {
+ "textfont": str(self.textFont),
+ "textsize": str(self.textSize),
+ "fixedwidth": str(self.textFixedW),
+ "width": str(self.textWidth),
+ "margin": str(self.textMargin),
+ "tabwidth": str(self.tabWidth),
+ "focuswidth": str(self.focusWidth),
+ "hidefocusfooter": str(self.hideFocusFooter),
+ "justify": str(self.doJustify),
+ "autoselect": str(self.autoSelect),
+ "autoreplace": str(self.doReplace),
+ "repsquotes": str(self.doReplaceSQuote),
+ "repdquotes": str(self.doReplaceDQuote),
+ "repdash": str(self.doReplaceDash),
+ "repdots": str(self.doReplaceDots),
+ "scrollpastend": str(self.scrollPastEnd),
+ "autoscroll": str(self.autoScroll),
+ "autoscrollpos": str(self.autoScrollPos),
+ "fmtsinglequote": self._packList(self.fmtSingleQuotes),
+ "fmtdoublequote": self._packList(self.fmtDoubleQuotes),
+ "fmtpadbefore": str(self.fmtPadBefore),
+ "fmtpadafter": str(self.fmtPadAfter),
+ "fmtpadthin": str(self.fmtPadThin),
+ "spelltool": str(self.spellTool),
+ "spellcheck": str(self.spellLanguage),
+ "showtabsnspaces": str(self.showTabsNSpaces),
+ "showlineendings": str(self.showLineEndings),
+ "showmultispaces": str(self.showMultiSpaces),
+ "bigdoclimit": str(self.bigDocLimit),
+ "showfullpath": str(self.showFullPath),
+ "highlightquotes": str(self.highlightQuotes),
+ "allowopensquote": str(self.allowOpenSQuote),
+ "allowopendquote": str(self.allowOpenDQuote),
+ "highlightemph": str(self.highlightEmph),
+ "stopwhenidle": str(self.stopWhenIdle),
+ "useridletime": str(self.userIdleTime),
+ }
- # Editor
- cnfSec = "Editor"
- cnfParse.add_section(cnfSec)
- cnfParse.set(cnfSec, "textfont", str(self.textFont))
- cnfParse.set(cnfSec, "textsize", str(self.textSize))
- cnfParse.set(cnfSec, "fixedwidth", str(self.textFixedW))
- cnfParse.set(cnfSec, "width", str(self.textWidth))
- cnfParse.set(cnfSec, "margin", str(self.textMargin))
- cnfParse.set(cnfSec, "tabwidth", str(self.tabWidth))
- cnfParse.set(cnfSec, "focuswidth", str(self.focusWidth))
- cnfParse.set(cnfSec, "hidefocusfooter", str(self.hideFocusFooter))
- cnfParse.set(cnfSec, "justify", str(self.doJustify))
- cnfParse.set(cnfSec, "autoselect", str(self.autoSelect))
- cnfParse.set(cnfSec, "autoreplace", str(self.doReplace))
- cnfParse.set(cnfSec, "repsquotes", str(self.doReplaceSQuote))
- cnfParse.set(cnfSec, "repdquotes", str(self.doReplaceDQuote))
- cnfParse.set(cnfSec, "repdash", str(self.doReplaceDash))
- cnfParse.set(cnfSec, "repdots", str(self.doReplaceDots))
- cnfParse.set(cnfSec, "scrollpastend", str(self.scrollPastEnd))
- cnfParse.set(cnfSec, "autoscroll", str(self.autoScroll))
- cnfParse.set(cnfSec, "autoscrollpos", str(self.autoScrollPos))
- cnfParse.set(cnfSec, "fmtsinglequote", self._packList(self.fmtSingleQuotes))
- cnfParse.set(cnfSec, "fmtdoublequote", self._packList(self.fmtDoubleQuotes))
- cnfParse.set(cnfSec, "fmtpadbefore", str(self.fmtPadBefore))
- cnfParse.set(cnfSec, "fmtpadafter", str(self.fmtPadAfter))
- cnfParse.set(cnfSec, "fmtpadthin", str(self.fmtPadThin))
- cnfParse.set(cnfSec, "spelltool", str(self.spellTool))
- cnfParse.set(cnfSec, "spellcheck", str(self.spellLanguage))
- cnfParse.set(cnfSec, "showtabsnspaces", str(self.showTabsNSpaces))
- cnfParse.set(cnfSec, "showlineendings", str(self.showLineEndings))
- cnfParse.set(cnfSec, "showmultispaces", str(self.showMultiSpaces))
- cnfParse.set(cnfSec, "bigdoclimit", str(self.bigDocLimit))
- cnfParse.set(cnfSec, "showfullpath", str(self.showFullPath))
- cnfParse.set(cnfSec, "highlightquotes", str(self.highlightQuotes))
- cnfParse.set(cnfSec, "allowopensquote", str(self.allowOpenSQuote))
- cnfParse.set(cnfSec, "allowopendquote", str(self.allowOpenDQuote))
- cnfParse.set(cnfSec, "highlightemph", str(self.highlightEmph))
- cnfParse.set(cnfSec, "stopwhenidle", str(self.stopWhenIdle))
- cnfParse.set(cnfSec, "useridletime", str(self.userIdleTime))
+ theConf["Backup"] = {
+ "backuppath": str(self.backupPath),
+ "backuponclose": str(self.backupOnClose),
+ "askbeforebackup": str(self.askBeforeBackup),
+ }
- # Backup
- cnfSec = "Backup"
- cnfParse.add_section(cnfSec)
- cnfParse.set(cnfSec, "backuppath", str(self.backupPath))
- cnfParse.set(cnfSec, "backuponclose", str(self.backupOnClose))
- cnfParse.set(cnfSec, "askbeforebackup", str(self.askBeforeBackup))
+ theConf["State"] = {
+ "showrefpanel": str(self.showRefPanel),
+ "viewcomments": str(self.viewComments),
+ "viewsynopsis": str(self.viewSynopsis),
+ "searchcase": str(self.searchCase),
+ "searchword": str(self.searchWord),
+ "searchregex": str(self.searchRegEx),
+ "searchloop": str(self.searchLoop),
+ "searchnextfile": str(self.searchNextFile),
+ "searchmatchcap": str(self.searchMatchCap),
+ }
- # State
- cnfSec = "State"
- cnfParse.add_section(cnfSec)
- cnfParse.set(cnfSec, "showrefpanel", str(self.showRefPanel))
- cnfParse.set(cnfSec, "viewcomments", str(self.viewComments))
- cnfParse.set(cnfSec, "viewsynopsis", str(self.viewSynopsis))
- cnfParse.set(cnfSec, "searchcase", str(self.searchCase))
- cnfParse.set(cnfSec, "searchword", str(self.searchWord))
- cnfParse.set(cnfSec, "searchregex", str(self.searchRegEx))
- cnfParse.set(cnfSec, "searchloop", str(self.searchLoop))
- cnfParse.set(cnfSec, "searchnextfile", str(self.searchNextFile))
- cnfParse.set(cnfSec, "searchmatchcap", str(self.searchMatchCap))
-
- # Path
- cnfSec = "Path"
- cnfParse.add_section(cnfSec)
- cnfParse.set(cnfSec, "lastpath", str(self.lastPath))
+ theConf["Path"] = {
+ "lastpath": str(self.lastPath),
+ }
# Write config file
cnfPath = os.path.join(self.confPath, self.confFile)
try:
with open(cnfPath, mode="w", encoding="utf-8") as outFile:
- cnfParse.write(outFile)
+ theConf.write(outFile)
self.confChanged = False
except Exception as e:
logger.error("Could not save config file")
@@ -884,7 +726,7 @@ class Config:
return True
def updateRecentCache(self, projPath, projTitle, wordCount, saveTime):
- """Add or update recent cache information o9n a given project.
+ """Add or update recent cache information on a given project.
"""
self.recentProj[os.path.abspath(projPath)] = {
"title": projTitle,
@@ -992,12 +834,6 @@ class Config:
self.confChanged = True
return self.showRefPanel
- def getErrData(self):
- errMessage = "
".join(self.errData)
- self.hasError = False
- self.errData = []
- return errMessage
-
def setViewComments(self, viewState):
self.viewComments = viewState
self.confChanged = True
@@ -1051,6 +887,12 @@ class Config:
def getFocusWidth(self):
return self.pxInt(self.focusWidth)
+ def getErrData(self):
+ errMessage = "
".join(self.errData)
+ self.hasError = False
+ self.errData = []
+ return errMessage
+
##
# Internal Functions
##
@@ -1060,52 +902,8 @@ class Config:
"""
return ", ".join([str(inVal) for inVal in inData])
- def _unpackList(self, inStr, listDefault, cnfType):
- """Unpack a comma-separated string of items into a list.
- """
- inData = inStr.split(",")
- outData = listDefault.copy()
- for i in range(min(len(inData), len(listDefault))):
- try:
- if cnfType == self.CNF_S_LST:
- outData[i] = inData[i].strip()
- elif cnfType == self.CNF_I_LST:
- outData[i] = int(inData[i].strip())
- else:
- continue
- except Exception:
- continue
- return outData
-
- def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault):
- """Parse a line and return the correct datatype.
- """
- if cnfParse.has_section(cnfSec):
- if cnfParse.has_option(cnfSec, cnfName):
- try:
- if cnfType == self.CNF_STR:
- return cnfParse.get(cnfSec, cnfName)
- elif cnfType == self.CNF_INT:
- return cnfParse.getint(cnfSec, cnfName)
- elif cnfType == self.CNF_BOOL:
- return cnfParse.getboolean(cnfSec, cnfName)
- elif cnfType == self.CNF_I_LST:
- return self._unpackList(
- cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_I_LST
- )
- elif cnfType == self.CNF_S_LST:
- return self._unpackList(
- cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_S_LST
- )
- except ValueError:
- logger.error("Failed to load value from config file.")
- logException()
- return cnfDefault
-
- return cnfDefault
-
def _checkNone(self, checkVal):
- """Return a NoneType if the value correspomds to None, otherwise
+ """Return a NoneType if the value corresponds to None, otherwise
return the value unchanged.
"""
if checkVal is None:
diff --git a/nw/core/index.py b/nw/core/index.py
index 01bdb69d..d3cfc18e 100644
--- a/nw/core/index.py
+++ b/nw/core/index.py
@@ -257,10 +257,10 @@ class NWIndex():
# If the file is archived or trashed, we don't index the file itself
if self.theProject.projTree.isTrashRoot(theItem.itemParent):
- logger.info("Not indexing trash item '%s'", tHandle)
+ logger.debug("Not indexing trash item '%s'", tHandle)
return False
if theRoot.itemClass == nwItemClass.ARCHIVE:
- logger.info("Not indexing archived item '%s'", tHandle)
+ logger.debug("Not indexing archived item '%s'", tHandle)
return False
itemClass = theItem.itemClass
diff --git a/nw/core/item.py b/nw/core/item.py
index 9da762c0..a61e8a0b 100644
--- a/nw/core/item.py
+++ b/nw/core/item.py
@@ -251,7 +251,7 @@ class NWItem():
if isinstance(expState, str):
self.isExpanded = (expState == str(True))
else:
- self.isExpanded = (expState == True) # noqa: E712
+ self.isExpanded = (expState is True)
return
def setExported(self, expState):
@@ -260,7 +260,7 @@ class NWItem():
if isinstance(expState, str):
self.isExported = (expState == str(True))
else:
- self.isExported = (expState == True) # noqa: E712
+ self.isExported = (expState is True)
return
##
diff --git a/nw/core/project.py b/nw/core/project.py
index 752178af..ccdfcc45 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -369,7 +369,7 @@ class NWProject():
self.clearProject()
self.projPath = os.path.abspath(os.path.dirname(fileName))
- logger.debug("Opening project: %s", self.projPath)
+ logger.info("Opening project: %s", self.projPath)
# Standard Folders and Files
# ==========================
@@ -611,7 +611,7 @@ class NWProject():
if not self.ensureFolderStructure():
return False
- logger.debug("Saving project: %s", self.projPath)
+ logger.info("Saving project: %s", self.projPath)
if autoSave:
self.autoCount += 1
@@ -712,6 +712,7 @@ class NWProject():
def closeProject(self, idleTime=0):
"""Close the current project and clear all meta data.
"""
+ logger.info("Closing project: %s", self.projPath)
self.optState.saveSettings()
self.projTree.writeToCFile()
self._appendSessionStats(idleTime)
diff --git a/nw/gui/theme.py b/nw/gui/theme.py
index 1932ace8..d493aae9 100644
--- a/nw/gui/theme.py
+++ b/nw/gui/theme.py
@@ -27,7 +27,6 @@ along with this program. If not, see .
import nw
import os
import logging
-import configparser
from math import ceil
from functools import partial
@@ -39,6 +38,7 @@ from PyQt5.QtGui import (
)
from nw.enum import nwAlert
+from nw.common import NWConfigParser
logger = logging.getLogger(__name__)
@@ -280,7 +280,7 @@ class GuiTheme:
return False
# Config File
- confParser = configparser.ConfigParser()
+ confParser = NWConfigParser()
try:
with open(self.confFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile)
@@ -292,13 +292,13 @@ class GuiTheme:
# Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
- self.themeName = self._parseLine(confParser, cnfSec, "name", "")
- self.themeDescription = self._parseLine(confParser, cnfSec, "description", "N/A")
- self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A")
- self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A")
- self.themeUrl = self._parseLine(confParser, cnfSec, "url", "")
- self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A")
- self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
+ self.themeName = confParser.rdStr(cnfSec, "name", "")
+ self.themeDescription = confParser.rdStr(cnfSec, "description", "N/A")
+ self.themeAuthor = confParser.rdStr(cnfSec, "author", "N/A")
+ self.themeCredit = confParser.rdStr(cnfSec, "credit", "N/A")
+ self.themeUrl = confParser.rdStr(cnfSec, "url", "")
+ self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
+ self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
# Palette
cnfSec = "Palette"
@@ -338,7 +338,7 @@ class GuiTheme:
"""
logger.debug("Loading syntax theme files")
- confParser = configparser.ConfigParser()
+ confParser = NWConfigParser()
try:
with open(self.syntaxFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile)
@@ -350,13 +350,13 @@ class GuiTheme:
# Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
- self.syntaxName = self._parseLine(confParser, cnfSec, "name", "")
- self.syntaxDescription = self._parseLine(confParser, cnfSec, "description", "")
- self.syntaxAuthor = self._parseLine(confParser, cnfSec, "author", "")
- self.syntaxCredit = self._parseLine(confParser, cnfSec, "credit", "")
- self.syntaxUrl = self._parseLine(confParser, cnfSec, "url", "")
- self.syntaxLicense = self._parseLine(confParser, cnfSec, "license", "")
- self.syntaxLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
+ self.syntaxName = confParser.rdStr(cnfSec, "name", "")
+ self.syntaxDescription = confParser.rdStr(cnfSec, "description", "")
+ self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "")
+ self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "")
+ self.syntaxUrl = confParser.rdStr(cnfSec, "url", "")
+ self.syntaxLicense = confParser.rdStr(cnfSec, "license", "")
+ self.syntaxLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
# Syntax
cnfSec = "Syntax"
@@ -388,7 +388,7 @@ class GuiTheme:
if self.themeList:
return self.themeList
- confParser = configparser.ConfigParser()
+ confParser = NWConfigParser()
for themeDir in os.listdir(os.path.join(self.mainConf.themeRoot, self.guiPath)):
themeConf = os.path.join(
self.mainConf.themeRoot, self.guiPath, themeDir, self.confName
@@ -420,7 +420,7 @@ class GuiTheme:
if self.syntaxList:
return self.syntaxList
- confParser = configparser.ConfigParser()
+ confParser = NWConfigParser()
syntaxDir = os.path.join(self.mainConf.themeRoot, self.syntaxPath)
for syntaxFile in os.listdir(syntaxDir):
syntaxPath = os.path.join(syntaxDir, syntaxFile)
@@ -486,15 +486,6 @@ class GuiTheme:
self.guiPalette.setColor(paletteVal, QColor(*readCol))
return
- def _parseLine(self, confParser, cnfSec, cnfName, cnfDefault):
- """Simple wrapper for the config parser to check that the entry
- exists before attempting to load it.
- """
- if confParser.has_section(cnfSec):
- if confParser.has_option(cnfSec, cnfName):
- return confParser.get(cnfSec, cnfName)
- return cnfDefault
-
# End Class GuiTheme
@@ -644,7 +635,7 @@ class GuiIcons:
return False
# Config File
- confParser = configparser.ConfigParser()
+ confParser = NWConfigParser()
try:
with open(self.confFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile)
@@ -656,13 +647,13 @@ class GuiIcons:
# Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
- self.themeName = self._parseLine(confParser, cnfSec, "name", "")
- self.themeDescription = self._parseLine(confParser, cnfSec, "description", "")
- self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A")
- self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A")
- self.themeUrl = self._parseLine(confParser, cnfSec, "url", "")
- self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A")
- self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
+ self.themeName = confParser.rdStr(cnfSec, "name", "")
+ self.themeDescription = confParser.rdStr(cnfSec, "description", "")
+ self.themeAuthor = confParser.rdStr(cnfSec, "author", "N/A")
+ self.themeCredit = confParser.rdStr(cnfSec, "credit", "N/A")
+ self.themeUrl = confParser.rdStr(cnfSec, "url", "")
+ self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
+ self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
# Palette
cnfSec = "Map"
@@ -736,7 +727,7 @@ class GuiIcons:
if self.themeList:
return self.themeList
- confParser = configparser.ConfigParser()
+ confParser = NWConfigParser()
for themeDir in os.listdir(self.mainConf.iconPath):
themePath = os.path.join(self.mainConf.iconPath, themeDir)
if not os.path.isdir(themePath) or themeDir == self.fbackName:
@@ -818,13 +809,4 @@ class GuiIcons:
return QIcon()
- def _parseLine(self, confParser, cnfSec, cnfName, cnfDefault):
- """Simple wrapper for the config parser to check that the entry
- exists before attempting to load it.
- """
- if confParser.has_section(cnfSec):
- if confParser.has_option(cnfSec, cnfName):
- return confParser.get(cnfSec, cnfName)
- return cnfDefault
-
# END Class GuiIcons
diff --git a/nw/guimain.py b/nw/guimain.py
index c485988f..3ca7ef7d 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -71,12 +71,9 @@ class GuiMain(QMainWindow):
logger.info("OS: %s", self.mainConf.osType)
logger.info("Kernel: %s", self.mainConf.kernelVer)
logger.info("Host: %s", self.mainConf.hostName)
- logger.info("Qt5 Version: %s (%d)",
- self.mainConf.verQtString, self.mainConf.verQtValue)
- logger.info("PyQt5 Version: %s (%d)",
- self.mainConf.verPyQtString, self.mainConf.verPyQtValue)
- logger.info("Python Version: %s (0x%x)",
- self.mainConf.verPyString, self.mainConf.verPyHexVal)
+ logger.info("Qt5: %s (%d)", self.mainConf.verQtString, self.mainConf.verQtValue)
+ logger.info("PyQt5: %s (%d)", self.mainConf.verPyQtString, self.mainConf.verPyQtValue)
+ logger.info("Python: %s (0x%x)", self.mainConf.verPyString, self.mainConf.verPyHexVal)
logger.info("GUI Language: %s", self.mainConf.guiLang)
# Core Classes
@@ -297,7 +294,7 @@ class GuiMain(QMainWindow):
logger.debug("Opening project from additional command line option")
self.openProject(self.mainConf.cmdOpen)
- logger.debug("novelWriter is ready ...")
+ logger.info("novelWriter is ready ...")
self.setStatus(self.tr("novelWriter is ready ..."))
return
@@ -872,7 +869,7 @@ class GuiMain(QMainWindow):
logger.error("No project open")
return False
- logger.debug("Rebuilding index ...")
+ logger.info("Rebuilding index ...")
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
tStart = time()
diff --git a/tests/conftest.py b/tests/conftest.py
index 93ed72a1..a1e97928 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -91,10 +91,7 @@ def fncDir(tmpDir):
shutil.rmtree(fncDir)
if not os.path.isdir(fncDir):
os.mkdir(fncDir)
- yield fncDir
- if os.path.isdir(fncDir):
- shutil.rmtree(fncDir)
- return
+ return fncDir
@pytest.fixture(scope="function")
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index e5fb98ce..f85aaf06 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -19,14 +19,19 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import os
import time
import pytest
+from datetime import datetime
+
+from tools import writeFile
+
from nw.common import (
- checkString, checkBool, checkInt, formatInt, transferCase,
- fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt,
+ checkString, checkBool, checkInt, formatInt, transferCase, fuzzyTime,
+ checkHandle, formatTimeStamp, parseTimeStamp, formatTime, hexToInt,
makeFileNameSafe, isHandle, isTitleTag, isItemClass, isItemType,
- isItemLayout, numberToRoman
+ isItemLayout, numberToRoman, NWConfigParser
)
@@ -65,11 +70,11 @@ def testBaseCommon_CheckBool():
"""
assert checkBool(None, 3, True) is None
assert checkBool("None", 3, True) is None
- assert checkBool("True", False, False)
- assert not checkBool("False", True, False)
+ assert checkBool("True", False, False) is True
+ assert checkBool("False", True, False) is False
assert checkBool("Boo", None, False) is None
- assert not checkBool(0, None, False)
- assert checkBool(1, None, False)
+ assert checkBool(0, None, False) is False
+ assert checkBool(1, None, False) is True
assert checkBool(2, None, False) is None
assert checkBool(0.0, None, False) is None
assert checkBool(1.0, None, False) is None
@@ -96,13 +101,12 @@ def testBaseCommon_CheckHandle():
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")
+ assert isHandle("47666c91c7ccf") is True
+ assert isHandle("47666C91C7CCF") is False
+ assert isHandle("h7666c91c7ccf") is False
+ assert isHandle("None") is False
+ assert isHandle(None) is False
+ assert isHandle("STUFF") is False
# END Test testBaseCommon_IsHandle
@@ -111,16 +115,16 @@ def testBaseCommon_IsHandle():
def testBaseCommon_IsTitleTag():
"""Test the isItemClass function.
"""
- assert isTitleTag("T123456")
+ assert isTitleTag("T123456") is True
- assert not isTitleTag("t123456")
- assert not isTitleTag("S123456")
- assert not isTitleTag("T12345A")
- assert not isTitleTag("T1234567")
+ assert isTitleTag("t123456") is False
+ assert isTitleTag("S123456") is False
+ assert isTitleTag("T12345A") is False
+ assert isTitleTag("T1234567") is False
- assert not isTitleTag("None")
- assert not isTitleTag(None)
- assert not isTitleTag("STUFF")
+ assert isTitleTag("None") is False
+ assert isTitleTag(None) is False
+ assert isTitleTag("STUFF") is False
# END Test testBaseCommon_IsTitleTag
@@ -129,21 +133,21 @@ def testBaseCommon_IsTitleTag():
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 isItemClass("NO_CLASS") is True
+ assert isItemClass("NOVEL") is True
+ assert isItemClass("PLOT") is True
+ assert isItemClass("CHARACTER") is True
+ assert isItemClass("WORLD") is True
+ assert isItemClass("TIMELINE") is True
+ assert isItemClass("OBJECT") is True
+ assert isItemClass("ENTITY") is True
+ assert isItemClass("CUSTOM") is True
+ assert isItemClass("ARCHIVE") is True
+ assert isItemClass("TRASH") is True
- assert not isItemClass("None")
- assert not isItemClass(None)
- assert not isItemClass("STUFF")
+ assert isItemClass("None") is False
+ assert isItemClass(None) is False
+ assert isItemClass("STUFF") is False
# END Test testBaseCommon_IsItemClass
@@ -152,15 +156,15 @@ def testBaseCommon_IsItemClass():
def testBaseCommon_IsItemType():
"""Test the isItemType function.
"""
- assert isItemType("NO_TYPE")
- assert isItemType("ROOT")
- assert isItemType("FOLDER")
- assert isItemType("FILE")
- assert isItemType("TRASH")
+ assert isItemType("NO_TYPE") is True
+ assert isItemType("ROOT") is True
+ assert isItemType("FOLDER") is True
+ assert isItemType("FILE") is True
+ assert isItemType("TRASH") is True
- assert not isItemType("None")
- assert not isItemType(None)
- assert not isItemType("STUFF")
+ assert isItemType("None") is False
+ assert isItemType(None) is False
+ assert isItemType("STUFF") is False
# END Test testBaseCommon_IsItemType
@@ -169,19 +173,19 @@ def testBaseCommon_IsItemType():
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 isItemLayout("NO_LAYOUT") is True
+ assert isItemLayout("TITLE") is True
+ assert isItemLayout("BOOK") is True
+ assert isItemLayout("PAGE") is True
+ assert isItemLayout("PARTITION") is True
+ assert isItemLayout("UNNUMBERED") is True
+ assert isItemLayout("CHAPTER") is True
+ assert isItemLayout("SCENE") is True
+ assert isItemLayout("NOTE") is True
- assert not isItemLayout("None")
- assert not isItemLayout(None)
- assert not isItemLayout("STUFF")
+ assert isItemLayout("None") is False
+ assert isItemLayout(None) is False
+ assert isItemLayout("STUFF") is False
# END Test testBaseCommon_IsItemLayout
@@ -234,11 +238,29 @@ def testBaseCommon_FormatTime():
# END Test testBaseCommon_FormatTime
+@pytest.mark.base
+def testBaseCommon_ParseTimeStamp():
+ """Test the parseTimeStamp function.
+ """
+ localEpoch = datetime(2000, 1, 1).timestamp()
+ assert parseTimeStamp(None, 0.0, allowNone=True) is None
+ assert parseTimeStamp("None", 0.0, allowNone=True) is None
+ assert parseTimeStamp("None", 0.0) == 0.0
+ assert parseTimeStamp("2000-01-01 00:00:00", 123.0) == localEpoch
+ assert parseTimeStamp("2000-13-01 00:00:00", 123.0) == 123.0
+ assert parseTimeStamp("2000-01-32 00:00:00", 123.0) == 123.0
+
+# END Test testBaseCommon_ParseTimeStamp
+
+
@pytest.mark.base
def testBaseCommon_FormatInt():
"""Test the formatInt function.
"""
- assert formatInt(1000) == "1000"
+ # Normal Cases
+ assert formatInt(1) == "1"
+ assert formatInt(12) == "12"
+ assert formatInt(123) == "123"
assert formatInt(1234) == "1.23\u2009k"
assert formatInt(12345) == "12.3\u2009k"
assert formatInt(123456) == "123\u2009k"
@@ -247,6 +269,11 @@ def testBaseCommon_FormatInt():
assert formatInt(123456789) == "123\u2009M"
assert formatInt(1234567890) == "1.23\u2009G"
+ # Exceptions
+ assert formatInt(12.3) == "ERR"
+ assert formatInt(None) == "ERR"
+ assert formatInt("42") == "ERR"
+
# END Test testBaseCommon_FormatInt
@@ -339,3 +366,87 @@ def testBaseCommon_RomanNumbers():
assert numberToRoman(999, True) == "cmxcix"
# END Test testBaseCommon_RomanNumbers
+
+
+@pytest.mark.base
+def testBaseCommon_NWConfigParser(fncDir):
+ """Test the NWConfigParser subclass.
+ """
+ tstConf = os.path.join(fncDir, "test.cfg")
+ writeFile(tstConf, (
+ "[main]\n"
+ "stropt = value\n"
+ "intopt1 = 42\n"
+ "intopt2 = 42.43\n"
+ "boolopt1 = true\n"
+ "boolopt2 = TRUE\n"
+ "boolopt3 = 1\n"
+ "boolopt4 = 0\n"
+ "list1 = a, b, c\n"
+ "list2 = 17, 18, 19\n"
+ ))
+
+ cfgParser = NWConfigParser()
+ cfgParser.read(tstConf)
+
+ # Readers
+ # =======
+
+ # Read String
+ assert cfgParser.rdStr("main", "stropt", "stuff") == "value"
+ assert cfgParser.rdStr("main", "boolopt1", "stuff") == "true"
+ assert cfgParser.rdStr("main", "intopt1", "stuff") == "42"
+
+ assert cfgParser.rdStr("nope", "stropt", "stuff") == "stuff"
+ assert cfgParser.rdStr("main", "blabla", "stuff") == "stuff"
+
+ # Read Boolean
+ assert cfgParser.rdBool("main", "boolopt1", None) is True
+ assert cfgParser.rdBool("main", "boolopt2", None) is True
+ assert cfgParser.rdBool("main", "boolopt3", None) is True
+ assert cfgParser.rdBool("main", "boolopt4", None) is False
+ assert cfgParser.rdBool("main", "intopt1", None) is None
+
+ assert cfgParser.rdBool("nope", "boolopt1", None) is None
+ assert cfgParser.rdBool("main", "blabla", None) is None
+
+ # Read Integer
+ assert cfgParser.rdInt("main", "intopt1", 13) == 42
+ assert cfgParser.rdInt("main", "intopt2", 13) == 13
+ assert cfgParser.rdInt("main", "stropt", 13) == 13
+
+ assert cfgParser.rdInt("nope", "intopt1", 13) == 13
+ assert cfgParser.rdInt("main", "blabla", 13) == 13
+
+ # Read String List
+ assert cfgParser.rdStrList("main", "list1", []) == []
+ assert cfgParser.rdStrList("main", "list1", ["x"]) == ["a"]
+ assert cfgParser.rdStrList("main", "list1", ["x", "y"]) == ["a", "b"]
+ assert cfgParser.rdStrList("main", "list1", ["x", "y", "z"]) == ["a", "b", "c"]
+ assert cfgParser.rdStrList("main", "list1", ["x", "y", "z", "w"]) == ["a", "b", "c", "w"]
+
+ assert cfgParser.rdStrList("main", "stropt", ["x"]) == ["value"]
+ assert cfgParser.rdStrList("main", "intopt1", ["x"]) == ["42"]
+
+ assert cfgParser.rdStrList("nope", "list1", ["x"]) == ["x"]
+ assert cfgParser.rdStrList("main", "blabla", ["x"]) == ["x"]
+
+ # Read Integer List
+ assert cfgParser.rdIntList("main", "list2", []) == []
+ assert cfgParser.rdIntList("main", "list2", [1]) == [17]
+ assert cfgParser.rdIntList("main", "list2", [1, 2]) == [17, 18]
+ assert cfgParser.rdIntList("main", "list2", [1, 2, 3]) == [17, 18, 19]
+ assert cfgParser.rdIntList("main", "list2", [1, 2, 3, 4]) == [17, 18, 19, 4]
+
+ assert cfgParser.rdIntList("main", "stropt", [1]) == [1]
+ assert cfgParser.rdIntList("main", "boolopt1", [1]) == [1]
+
+ assert cfgParser.rdIntList("nope", "list2", [1]) == [1]
+ assert cfgParser.rdIntList("main", "blabla", [1]) == [1]
+
+ # Internal
+ # ========
+
+ assert cfgParser._parseLine("main", "stropt", None, 999) is None
+
+# END Test testBaseCommon_NWConfigParser
diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py
index 14e24ec3..6dff0402 100644
--- a/tests/test_base/test_base_config.py
+++ b/tests/test_base/test_base_config.py
@@ -19,10 +19,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import pytest
-import sys
import os
-import configparser
+import sys
+import pytest
from shutil import copyfile
@@ -161,6 +160,20 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
assert tstConf.hasError is False
assert tstConf.errData == []
+ # Check handling of novelWriter as a package
+ with monkeypatch.context() as mp:
+ tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
+ assert tstConf.confPath == tmpDir
+ assert tstConf.dataPath == tmpDir
+ appRoot = tstConf.appRoot
+
+ mp.setattr("os.path.isfile", lambda *a: True)
+ tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
+ assert tstConf.confPath == tmpDir
+ assert tstConf.dataPath == tmpDir
+ assert tstConf.appRoot == os.path.dirname(appRoot)
+ assert tstConf.appPath == os.path.dirname(appRoot)
+
assert tstConf.loadConfig()
assert tstConf.saveConfig()
@@ -187,19 +200,34 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
assert tstConf.saveConfig()
# Localisation
+ # ============
+
i18nDir = os.path.join(fncDir, "i18n")
os.mkdir(i18nDir)
os.mkdir(os.path.join(i18nDir, "stuff"))
tstConf.nwLangPath = i18nDir
- copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(fncDir, "nw_en_GB.qm"))
+ copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_en_GB.qm"))
writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "")
writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "")
tstApp = MockApp()
tstConf.initLocalisation(tstApp)
+
+ # Check Lists
theList = tstConf.listLanguages(tstConf.LANG_NW)
assert theList == [("en_GB", "British English")]
+ theList = tstConf.listLanguages(tstConf.LANG_PROJ)
+ assert theList == [("en", "English")]
+ theList = tstConf.listLanguages(None)
+ assert theList == []
+
+ # Add Language
+ copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_fr.qm"))
+ writeFile(os.path.join(i18nDir, "nw_fr.ts"), "")
+
+ theList = tstConf.listLanguages(tstConf.LANG_NW)
+ assert theList == [("en_GB", "British English"), ("fr", "Français")]
copyfile(confFile, testFile)
assert cmpFiles(testFile, compFile, [2, 9, 10])
@@ -311,6 +339,7 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
# GUI Scaling
# ===========
+
tmpConf.guiScale = 1.0
assert tmpConf.pxInt(10) == 10
assert tmpConf.pxInt(13) == 13
@@ -343,6 +372,19 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
assert tmpConf.setWinSize(1200, 650)
+ # Preferences Size
+ tmpConf.guiScale = 2.0
+ assert tmpConf.setPreferencesSize(70, 70)
+ assert tmpConf.getPreferencesSize() == [70, 70]
+ assert tmpConf.prefGeometry == [35, 35]
+
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setPreferencesSize(70, 70)
+ assert tmpConf.getPreferencesSize() == [70, 70]
+ assert tmpConf.prefGeometry == [70, 70]
+
+ assert tmpConf.setPreferencesSize(700, 615)
+
# Project Tree Columns
tmpConf.guiScale = 2.0
assert tmpConf.setTreeColWidths([10, 20, 25])
@@ -436,6 +478,7 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
# Getters Only
# ============
+
tmpConf.guiScale = 1.0
assert tmpConf.getTextWidth() == 600
assert tmpConf.getTextMargin() == 40
@@ -450,6 +493,7 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
# Flag Setters
# ============
+
assert not tmpConf.setShowRefPanel(False)
assert not tmpConf.showRefPanel
assert tmpConf.setShowRefPanel(True)
@@ -482,74 +526,16 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
# Function _packList
assert tmpConf._packList(["A", 1, 2.0, None, False]) == "A, 1, 2.0, None, False"
- # Function _unpackList
- assert tmpConf._unpackList("1, 2, 3", [0, 0, 0], tmpConf.CNF_I_LST) == [1, 2, 3]
- assert tmpConf._unpackList("1, 2 ", [0, 0, 0], tmpConf.CNF_I_LST) == [1, 2, 0]
- assert tmpConf._unpackList("A, B, C", [0, 0, 0], tmpConf.CNF_I_LST) == [0, 0, 0]
- assert tmpConf._unpackList("1, 2, 3", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["1", "2", "3"]
- assert tmpConf._unpackList("A, B ", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["A", "B", "Z"]
- assert tmpConf._unpackList("A, B, C", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["A", "B", "C"]
- assert tmpConf._unpackList("A, B, C", ["X", "Y", "Z"], tmpConf.CNF_STR) == ["X", "Y", "Z"]
-
- # Function _parseLine
- cnfParse = configparser.ConfigParser()
- cnfParse.read_string(
- "[Main]\n"
- "val_string = stuff\n"
- "val_int = 123\n"
- "val_bool = True\n"
- "val_list_string = A, B, C\n"
- "val_list_int = 1, 2, 3\n"
- )
-
- assert tmpConf._parseLine(
- cnfParse, "Main", "val_string", tmpConf.CNF_STR, "default"
- ) == "stuff"
- assert tmpConf._parseLine(
- cnfParse, "Main", "nope", tmpConf.CNF_STR, "default"
- ) == "default"
-
- assert tmpConf._parseLine(
- cnfParse, "Main", "val_int", tmpConf.CNF_INT, "0"
- ) == 123
- assert tmpConf._parseLine(
- cnfParse, "Main", "nope", tmpConf.CNF_INT, 0
- ) == 0
- assert tmpConf._parseLine(
- cnfParse, "Main", "val_string", tmpConf.CNF_INT, 0
- ) == 0
-
- assert tmpConf._parseLine(
- cnfParse, "Main", "val_bool", tmpConf.CNF_BOOL, False
- ) is True
- assert tmpConf._parseLine(
- cnfParse, "Main", "nope", tmpConf.CNF_BOOL, False
- ) is False
- assert tmpConf._parseLine(
- cnfParse, "Main", "val_string", tmpConf.CNF_BOOL, False
- ) is False
-
- assert tmpConf._parseLine(
- cnfParse, "Main", "val_list_string", tmpConf.CNF_S_LST, ["W", "X", "Y", "Z"]
- ) == ["A", "B", "C", "Z"]
- assert tmpConf._parseLine(
- cnfParse, "Main", "nope", tmpConf.CNF_S_LST, ["W", "X", "Y", "Z"]
- ) == ["W", "X", "Y", "Z"]
-
- assert tmpConf._parseLine(
- cnfParse, "Main", "val_list_int", tmpConf.CNF_I_LST, [6, 7, 8, 9]
- ) == [1, 2, 3, 9]
- assert tmpConf._parseLine(
- cnfParse, "Main", "nope", tmpConf.CNF_S_LST, [6, 7, 8, 9]
- ) == [6, 7, 8, 9]
-
# Function _checkNone
assert tmpConf._checkNone(None) is None
assert tmpConf._checkNone("None") is None
- assert tmpConf._checkNone("stuff") == "stuff"
+ assert tmpConf._checkNone("none") is None
+ assert tmpConf._checkNone("NONE") is None
+ assert tmpConf._checkNone("NoNe") is None
+ assert tmpConf._checkNone(123456) == 123456
# Function _checkOptionalPackages
- # (Assumes enchant package exists ans is importable)
+ # (Assumes enchant package exists and is importable)
tmpConf._checkOptionalPackages()
assert tmpConf.hasEnchant is True
@@ -559,12 +545,12 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
assert tmpConf.hasEnchant is False
with monkeypatch.context() as mp:
- mp.setattr("shutil.which", lambda *args: "stuff")
+ mp.setattr("shutil.which", lambda *a: "stuff")
tmpConf._checkOptionalPackages()
assert tmpConf.hasAssistant is True
with monkeypatch.context() as mp:
- mp.setattr("shutil.which", lambda *args: None)
+ mp.setattr("shutil.which", lambda *a: None)
tmpConf._checkOptionalPackages()
assert tmpConf.hasAssistant is False