Config class improvements (#826)

* Subclass ConfigParser
* Remove redundant variables and update tests
* Update file description
* Move NWConfigParser class to common.py
* Improve coverage of config class
* Use NWConfigParser also in themes.py
* Update config class file header
* Fix comment
* Update the usage of ConfigParser to recommended practice and extend tests
* Improve logging for info level a bit
This commit is contained in:
Veronica Berglyd Olsen
2021-07-28 00:10:15 +02:00
committed by GitHub
parent e92506dc07
commit e5c715695e
11 changed files with 608 additions and 626 deletions
-1
View File
@@ -28,7 +28,6 @@ version = "1.5"
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = "1.5-alpha0" release = "1.5-alpha0"
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
os.environ["TZ"] = "Europe/Oslo" os.environ["TZ"] = "Europe/Oslo"
+174 -63
View File
@@ -26,24 +26,27 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging import logging
from datetime import datetime from datetime import datetime
from configparser import ConfigParser
from PyQt5.QtWidgets import qApp from PyQt5.QtWidgets import qApp
from PyQt5.QtCore import QCoreApplication from PyQt5.QtCore import QCoreApplication
from nw.enum import nwItemClass, nwItemType, nwItemLayout from nw.enum import nwItemClass, nwItemType, nwItemLayout
from nw.error import logException
from nw.constants import nwConst, nwUnicode from nw.constants import nwConst, nwUnicode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# =============================================================================================== #
# Checker Functions
# =============================================================================================== #
def checkString(value, default, allowNone=False): def checkString(value, default, allowNone=False):
"""Check if a variable is a string or a none. """Check if a variable is a string or a none.
""" """
if allowNone: if allowNone and (value is None or value == "None"):
if value is None: return None
return None
if value == "None":
return None
if isinstance(value, str): if isinstance(value, str):
return str(value) return str(value)
return default return default
@@ -52,11 +55,8 @@ def checkString(value, default, allowNone=False):
def checkInt(value, default, allowNone=False): def checkInt(value, default, allowNone=False):
"""Check if a variable is an integer or a none. """Check if a variable is an integer or a none.
""" """
if allowNone: if allowNone and (value is None or value == "None"):
if value is None: return None
return None
if value == "None":
return None
try: try:
return int(value) return int(value)
except Exception: except Exception:
@@ -66,11 +66,9 @@ def checkInt(value, default, allowNone=False):
def checkBool(value, default, allowNone=False): def checkBool(value, default, allowNone=False):
"""Check if a variable is a boolean or a none. """Check if a variable is a boolean or a none.
""" """
if allowNone: if allowNone and (value is None or value == "None"):
if value is None: return None
return None
if value == "None":
return None
if isinstance(value, str): if isinstance(value, str):
if value == "True": if value == "True":
return True return True
@@ -78,6 +76,7 @@ def checkBool(value, default, allowNone=False):
return False return False
else: else:
return default return default
elif isinstance(value, int): elif isinstance(value, int):
if value == 1: if value == 1:
return True return True
@@ -85,67 +84,69 @@ def checkBool(value, default, allowNone=False):
return False return False
else: else:
return default return default
return default return default
def checkHandle(value, default, allowNone=False): def checkHandle(value, default, allowNone=False):
"""Check if a value is a handle. """Check if a value is a handle.
""" """
if allowNone: if allowNone and (value is None or value == "None"):
if value is None: return None
return None
if value == "None":
return None
if isHandle(value): if isHandle(value):
return str(value) return str(value)
return default return default
def isHandle(theString): # =============================================================================================== #
# Validator Functions
# =============================================================================================== #
def isHandle(value):
"""Check if a string is a valid novelWriter handle. """Check if a string is a valid novelWriter handle.
Note: This is case sensitive. Must be lower case! Note: This is case sensitive. Must be lower case!
""" """
if not isinstance(theString, str): if not isinstance(value, str):
return False return False
if len(theString) != 13: if len(value) != 13:
return False return False
for c in theString: for c in value:
if c not in "0123456789abcdef": if c not in "0123456789abcdef":
return False return False
return True return True
def isTitleTag(theString): def isTitleTag(value):
"""Check if a string is a valid title string. """Check if a string is a valid title string.
""" """
if not isinstance(theString, str): if not isinstance(value, str):
return False return False
if len(theString) != 7: if len(value) != 7:
return False return False
if not theString.startswith("T"): if not value.startswith("T"):
return False return False
for c in theString[1:]: for c in value[1:]:
if c not in "0123456789": if c not in "0123456789":
return False return False
return True return True
def isItemClass(theString): def isItemClass(value):
"""Check if an item is a calid nwItemClass identifier. """Check if a string is a valid nwItemClass identifier.
""" """
return theString in nwItemClass.__members__ return value in nwItemClass.__members__
def isItemType(theString): def isItemType(value):
"""Check if an item is a calid nwItemType identifier. """Check if a string is a valid nwItemType identifier.
""" """
return theString in nwItemType.__members__ return value in nwItemType.__members__
def isItemLayout(theString): def isItemLayout(value):
"""Check if an item is a calid nwItemLayout identifier. """Check if a string is a valid nwItemLayout identifier.
""" """
return theString in nwItemLayout.__members__ return value in nwItemLayout.__members__
def hexToInt(value, default=0): def hexToInt(value, default=0):
@@ -159,14 +160,19 @@ def hexToInt(value, default=0):
return default return default
def formatInt(theInt): # =============================================================================================== #
# Formatting Functions
# =============================================================================================== #
def formatInt(value):
"""Formats an integer with k, M, G etc. """Formats an integer with k, M, G etc.
""" """
postFix = ["k", "M", "G", "T", "P", "E"] if not isinstance(value, int):
theVal = float(theInt) return "ERR"
theVal = float(value)
if theVal > 1000.0: if theVal > 1000.0:
for pF in postFix: for pF in ["k", "M", "G", "T", "P", "E"]:
theVal /= 1000.0 theVal /= 1000.0
if theVal < 1000.0: if theVal < 1000.0:
if theVal < 10.0: if theVal < 10.0:
@@ -176,7 +182,7 @@ def formatInt(theInt):
else: else:
return f"{theVal:3.0f}{nwUnicode.U_THSP}{pF}" return f"{theVal:3.0f}{nwUnicode.U_THSP}{pF}"
return str(theInt) return str(value)
def formatTimeStamp(theTime, fileSafe=False): def formatTimeStamp(theTime, fileSafe=False):
@@ -201,6 +207,23 @@ def formatTime(tS):
return "ERROR" 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): def splitVersionNumber(vString):
""" Splits a version string on the form aa.bb.cc into major, minor """ Splits a version string on the form aa.bb.cc into major, minor
and patch, and computes an integer value aabbcc. and patch, and computes an integer value aabbcc.
@@ -308,27 +331,8 @@ def fuzzyTime(secDiff):
).format(int(round(secDiff/31557600))) ).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): def numberToRoman(numVal, isLower=False):
"""Convert an integer to a roman number. """Convert an integer to a Roman number.
""" """
if not isinstance(numVal, int): if not isinstance(numVal, int):
return "NAN" return "NAN"
@@ -349,3 +353,110 @@ def numberToRoman(numVal, isLower=False):
break break
return romNum.lower() if isLower else romNum 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
+170 -372
View File
@@ -28,7 +28,6 @@ import sys
import json import json
import shutil import shutil
import logging import logging
import configparser
from time import time from time import time
@@ -39,7 +38,7 @@ from PyQt5.QtCore import (
) )
from nw.error import logException 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 from nw.constants import nwConst, nwFiles, nwUnicode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -47,12 +46,6 @@ logger = logging.getLogger(__name__)
class Config: class Config:
CNF_STR = 0
CNF_INT = 1
CNF_BOOL = 2
CNF_S_LST = 3
CNF_I_LST = 4
LANG_NW = 1 LANG_NW = 1
LANG_PROJ = 2 LANG_PROJ = 2
@@ -432,11 +425,11 @@ class Config:
if self.confPath is None: if self.confPath is None:
return False return False
cnfParse = configparser.ConfigParser() theConf = NWConfigParser()
cnfPath = os.path.join(self.confPath, self.confFile) cnfPath = os.path.join(self.confPath, self.confFile)
try: try:
with open(cnfPath, mode="r", encoding="utf-8") as inFile: with open(cnfPath, mode="r", encoding="utf-8") as inFile:
cnfParse.read_file(inFile) theConf.read_file(inFile)
except Exception as e: except Exception as e:
logger.error("Could not load config file") logger.error("Could not load config file")
logException() logException()
@@ -447,237 +440,95 @@ class Config:
# Main # Main
cnfSec = "Main" cnfSec = "Main"
self.guiTheme = self._parseLine( self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme)
cnfParse, cnfSec, "theme", self.CNF_STR, self.guiTheme self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax)
) self.guiIcons = theConf.rdStr(cnfSec, "icons", self.guiIcons)
self.guiSyntax = self._parseLine( self.guiDark = theConf.rdBool(cnfSec, "guidark", self.guiDark)
cnfParse, cnfSec, "syntax", self.CNF_STR, self.guiSyntax self.guiFont = theConf.rdStr(cnfSec, "guifont", self.guiFont)
) self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize", self.guiFontSize)
self.guiIcons = self._parseLine( self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes)
cnfParse, cnfSec, "icons", self.CNF_STR, self.guiIcons self.guiLang = theConf.rdStr(cnfSec, "guilang", self.guiLang)
)
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
)
# Sizes # Sizes
cnfSec = "Sizes" cnfSec = "Sizes"
self.winGeometry = self._parseLine( self.winGeometry = theConf.rdIntList(cnfSec, "geometry", self.winGeometry)
cnfParse, cnfSec, "geometry", self.CNF_I_LST, self.winGeometry self.prefGeometry = theConf.rdIntList(cnfSec, "preferences", self.prefGeometry)
) self.treeColWidth = theConf.rdIntList(cnfSec, "treecols", self.treeColWidth)
self.prefGeometry = self._parseLine( self.novelColWidth = theConf.rdIntList(cnfSec, "novelcols", self.novelColWidth)
cnfParse, cnfSec, "preferences", self.CNF_I_LST, self.prefGeometry self.projColWidth = theConf.rdIntList(cnfSec, "projcols", self.projColWidth)
) self.mainPanePos = theConf.rdIntList(cnfSec, "mainpane", self.mainPanePos)
self.treeColWidth = self._parseLine( self.docPanePos = theConf.rdIntList(cnfSec, "docpane", self.docPanePos)
cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth self.viewPanePos = theConf.rdIntList(cnfSec, "viewpane", self.viewPanePos)
) self.outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self.outlnPanePos)
self.novelColWidth = self._parseLine( self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen", self.isFullScreen)
cnfParse, cnfSec, "novelcols", self.CNF_I_LST, self.novelColWidth self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll", self.hideVScroll)
) self.hideHScroll = theConf.rdBool(cnfSec, "hidehscroll", self.hideHScroll)
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
)
# Project # Project
cnfSec = "Project" cnfSec = "Project"
self.autoSaveProj = self._parseLine( self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj)
cnfParse, cnfSec, "autosaveproject", self.CNF_INT, self.autoSaveProj self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc)
)
self.autoSaveDoc = self._parseLine(
cnfParse, cnfSec, "autosavedoc", self.CNF_INT, self.autoSaveDoc
)
# Editor # Editor
cnfSec = "Editor" cnfSec = "Editor"
self.textFont = self._parseLine( self.textFont = theConf.rdStr(cnfSec, "textfont", self.textFont)
cnfParse, cnfSec, "textfont", self.CNF_STR, self.textFont self.textSize = theConf.rdInt(cnfSec, "textsize", self.textSize)
) self.textFixedW = theConf.rdBool(cnfSec, "fixedwidth", self.textFixedW)
self.textSize = self._parseLine( self.textWidth = theConf.rdInt(cnfSec, "width", self.textWidth)
cnfParse, cnfSec, "textsize", self.CNF_INT, self.textSize self.textMargin = theConf.rdInt(cnfSec, "margin", self.textMargin)
) self.tabWidth = theConf.rdInt(cnfSec, "tabwidth", self.tabWidth)
self.textFixedW = self._parseLine( self.focusWidth = theConf.rdInt(cnfSec, "focuswidth", self.focusWidth)
cnfParse, cnfSec, "fixedwidth", self.CNF_BOOL, self.textFixedW self.hideFocusFooter = theConf.rdBool(cnfSec, "hidefocusfooter", self.hideFocusFooter)
) self.doJustify = theConf.rdBool(cnfSec, "justify", self.doJustify)
self.textWidth = self._parseLine( self.autoSelect = theConf.rdBool(cnfSec, "autoselect", self.autoSelect)
cnfParse, cnfSec, "width", self.CNF_INT, self.textWidth self.doReplace = theConf.rdBool(cnfSec, "autoreplace", self.doReplace)
) self.doReplaceSQuote = theConf.rdBool(cnfSec, "repsquotes", self.doReplaceSQuote)
self.textMargin = self._parseLine( self.doReplaceDQuote = theConf.rdBool(cnfSec, "repdquotes", self.doReplaceDQuote)
cnfParse, cnfSec, "margin", self.CNF_INT, self.textMargin self.doReplaceDash = theConf.rdBool(cnfSec, "repdash", self.doReplaceDash)
) self.doReplaceDots = theConf.rdBool(cnfSec, "repdots", self.doReplaceDots)
self.tabWidth = self._parseLine( self.scrollPastEnd = theConf.rdBool(cnfSec, "scrollpastend", self.scrollPastEnd)
cnfParse, cnfSec, "tabwidth", self.CNF_INT, self.tabWidth self.autoScroll = theConf.rdBool(cnfSec, "autoscroll", self.autoScroll)
) self.autoScrollPos = theConf.rdInt(cnfSec, "autoscrollpos", self.autoScrollPos)
self.focusWidth = self._parseLine( self.fmtSingleQuotes = theConf.rdStrList(cnfSec, "fmtsinglequote", self.fmtSingleQuotes)
cnfParse, cnfSec, "focuswidth", self.CNF_INT, self.focusWidth self.fmtDoubleQuotes = theConf.rdStrList(cnfSec, "fmtdoublequote", self.fmtDoubleQuotes)
) self.fmtPadBefore = theConf.rdStr(cnfSec, "fmtpadbefore", self.fmtPadBefore)
self.hideFocusFooter = self._parseLine( self.fmtPadAfter = theConf.rdStr(cnfSec, "fmtpadafter", self.fmtPadAfter)
cnfParse, cnfSec, "hidefocusfooter", self.CNF_BOOL, self.hideFocusFooter self.fmtPadThin = theConf.rdBool(cnfSec, "fmtpadthin", self.fmtPadThin)
) self.spellTool = theConf.rdStr(cnfSec, "spelltool", self.spellTool)
self.doJustify = self._parseLine( self.spellLanguage = theConf.rdStr(cnfSec, "spellcheck", self.spellLanguage)
cnfParse, cnfSec, "justify", self.CNF_BOOL, self.doJustify self.showTabsNSpaces = theConf.rdBool(cnfSec, "showtabsnspaces", self.showTabsNSpaces)
) self.showLineEndings = theConf.rdBool(cnfSec, "showlineendings", self.showLineEndings)
self.autoSelect = self._parseLine( self.showMultiSpaces = theConf.rdBool(cnfSec, "showmultispaces", self.showMultiSpaces)
cnfParse, cnfSec, "autoselect", self.CNF_BOOL, self.autoSelect self.bigDocLimit = theConf.rdInt(cnfSec, "bigdoclimit", self.bigDocLimit)
) self.showFullPath = theConf.rdBool(cnfSec, "showfullpath", self.showFullPath)
self.doReplace = self._parseLine( self.highlightQuotes = theConf.rdBool(cnfSec, "highlightquotes", self.highlightQuotes)
cnfParse, cnfSec, "autoreplace", self.CNF_BOOL, self.doReplace self.allowOpenSQuote = theConf.rdBool(cnfSec, "allowopensquote", self.allowOpenSQuote)
) self.allowOpenDQuote = theConf.rdBool(cnfSec, "allowopendquote", self.allowOpenDQuote)
self.doReplaceSQuote = self._parseLine( self.highlightEmph = theConf.rdBool(cnfSec, "highlightemph", self.highlightEmph)
cnfParse, cnfSec, "repsquotes", self.CNF_BOOL, self.doReplaceSQuote self.stopWhenIdle = theConf.rdBool(cnfSec, "stopwhenidle", self.stopWhenIdle)
) self.userIdleTime = theConf.rdInt(cnfSec, "useridletime", self.userIdleTime)
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
)
# Backup # Backup
cnfSec = "Backup" cnfSec = "Backup"
self.backupPath = self._parseLine( self.backupPath = theConf.rdStr(cnfSec, "backuppath", self.backupPath)
cnfParse, cnfSec, "backuppath", self.CNF_STR, self.backupPath self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose", self.backupOnClose)
) self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup", self.askBeforeBackup)
self.backupOnClose = self._parseLine(
cnfParse, cnfSec, "backuponclose", self.CNF_BOOL, self.backupOnClose
)
self.askBeforeBackup = self._parseLine(
cnfParse, cnfSec, "askbeforebackup", self.CNF_BOOL, self.askBeforeBackup
)
# State # State
cnfSec = "State" cnfSec = "State"
self.showRefPanel = self._parseLine( self.showRefPanel = theConf.rdBool(cnfSec, "showrefpanel", self.showRefPanel)
cnfParse, cnfSec, "showrefpanel", self.CNF_BOOL, self.showRefPanel self.viewComments = theConf.rdBool(cnfSec, "viewcomments", self.viewComments)
) self.viewSynopsis = theConf.rdBool(cnfSec, "viewsynopsis", self.viewSynopsis)
self.viewComments = self._parseLine( self.searchCase = theConf.rdBool(cnfSec, "searchcase", self.searchCase)
cnfParse, cnfSec, "viewcomments", self.CNF_BOOL, self.viewComments self.searchWord = theConf.rdBool(cnfSec, "searchword", self.searchWord)
) self.searchRegEx = theConf.rdBool(cnfSec, "searchregex", self.searchRegEx)
self.viewSynopsis = self._parseLine( self.searchLoop = theConf.rdBool(cnfSec, "searchloop", self.searchLoop)
cnfParse, cnfSec, "viewsynopsis", self.CNF_BOOL, self.viewSynopsis self.searchNextFile = theConf.rdBool(cnfSec, "searchnextfile", self.searchNextFile)
) self.searchMatchCap = theConf.rdBool(cnfSec, "searchmatchcap", self.searchMatchCap)
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
)
# Path # Path
cnfSec = "Path" cnfSec = "Path"
self.lastPath = self._parseLine( self.lastPath = theConf.rdStr(cnfSec, "lastpath", self.lastPath)
cnfParse, cnfSec, "lastpath", self.CNF_STR, self.lastPath
)
# Check Certain Values for None # Check Certain Values for None
self.spellLanguage = self._checkNone(self.spellLanguage) self.spellLanguage = self._checkNone(self.spellLanguage)
@@ -700,115 +551,106 @@ class Config:
if self.confPath is None: if self.confPath is None:
return False 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 theConf["Sizes"] = {
cnfSec = "Main" "geometry": self._packList(self.winGeometry),
cnfParse.add_section(cnfSec) "preferences": self._packList(self.prefGeometry),
cnfParse.set(cnfSec, "timestamp", formatTimeStamp(time())) "treecols": self._packList(self.treeColWidth),
cnfParse.set(cnfSec, "theme", str(self.guiTheme)) "novelcols": self._packList(self.novelColWidth),
cnfParse.set(cnfSec, "syntax", str(self.guiSyntax)) "projcols": self._packList(self.projColWidth),
cnfParse.set(cnfSec, "icons", str(self.guiIcons)) "mainpane": self._packList(self.mainPanePos),
cnfParse.set(cnfSec, "guidark", str(self.guiDark)) "docpane": self._packList(self.docPanePos),
cnfParse.set(cnfSec, "guifont", str(self.guiFont)) "viewpane": self._packList(self.viewPanePos),
cnfParse.set(cnfSec, "guifontsize", str(self.guiFontSize)) "outlinepane": self._packList(self.outlnPanePos),
cnfParse.set(cnfSec, "lastnotes", str(self.lastNotes)) "fullscreen": str(self.isFullScreen),
cnfParse.set(cnfSec, "guilang", str(self.guiLang)) "hidevscroll": str(self.hideVScroll),
"hidehscroll": str(self.hideHScroll),
}
# Sizes theConf["Project"] = {
cnfSec = "Sizes" "autosaveproject": str(self.autoSaveProj),
cnfParse.add_section(cnfSec) "autosavedoc": str(self.autoSaveDoc),
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))
# Project theConf["Editor"] = {
cnfSec = "Project" "textfont": str(self.textFont),
cnfParse.add_section(cnfSec) "textsize": str(self.textSize),
cnfParse.set(cnfSec, "autosaveproject", str(self.autoSaveProj)) "fixedwidth": str(self.textFixedW),
cnfParse.set(cnfSec, "autosavedoc", str(self.autoSaveDoc)) "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 theConf["Backup"] = {
cnfSec = "Editor" "backuppath": str(self.backupPath),
cnfParse.add_section(cnfSec) "backuponclose": str(self.backupOnClose),
cnfParse.set(cnfSec, "textfont", str(self.textFont)) "askbeforebackup": str(self.askBeforeBackup),
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))
# Backup theConf["State"] = {
cnfSec = "Backup" "showrefpanel": str(self.showRefPanel),
cnfParse.add_section(cnfSec) "viewcomments": str(self.viewComments),
cnfParse.set(cnfSec, "backuppath", str(self.backupPath)) "viewsynopsis": str(self.viewSynopsis),
cnfParse.set(cnfSec, "backuponclose", str(self.backupOnClose)) "searchcase": str(self.searchCase),
cnfParse.set(cnfSec, "askbeforebackup", str(self.askBeforeBackup)) "searchword": str(self.searchWord),
"searchregex": str(self.searchRegEx),
"searchloop": str(self.searchLoop),
"searchnextfile": str(self.searchNextFile),
"searchmatchcap": str(self.searchMatchCap),
}
# State theConf["Path"] = {
cnfSec = "State" "lastpath": str(self.lastPath),
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))
# Write config file # Write config file
cnfPath = os.path.join(self.confPath, self.confFile) cnfPath = os.path.join(self.confPath, self.confFile)
try: try:
with open(cnfPath, mode="w", encoding="utf-8") as outFile: with open(cnfPath, mode="w", encoding="utf-8") as outFile:
cnfParse.write(outFile) theConf.write(outFile)
self.confChanged = False self.confChanged = False
except Exception as e: except Exception as e:
logger.error("Could not save config file") logger.error("Could not save config file")
@@ -884,7 +726,7 @@ class Config:
return True return True
def updateRecentCache(self, projPath, projTitle, wordCount, saveTime): 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)] = { self.recentProj[os.path.abspath(projPath)] = {
"title": projTitle, "title": projTitle,
@@ -992,12 +834,6 @@ class Config:
self.confChanged = True self.confChanged = True
return self.showRefPanel return self.showRefPanel
def getErrData(self):
errMessage = "<br>".join(self.errData)
self.hasError = False
self.errData = []
return errMessage
def setViewComments(self, viewState): def setViewComments(self, viewState):
self.viewComments = viewState self.viewComments = viewState
self.confChanged = True self.confChanged = True
@@ -1051,6 +887,12 @@ class Config:
def getFocusWidth(self): def getFocusWidth(self):
return self.pxInt(self.focusWidth) return self.pxInt(self.focusWidth)
def getErrData(self):
errMessage = "<br>".join(self.errData)
self.hasError = False
self.errData = []
return errMessage
## ##
# Internal Functions # Internal Functions
## ##
@@ -1060,52 +902,8 @@ class Config:
""" """
return ", ".join([str(inVal) for inVal in inData]) 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): 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. return the value unchanged.
""" """
if checkVal is None: if checkVal is None:
+2 -2
View File
@@ -257,10 +257,10 @@ class NWIndex():
# If the file is archived or trashed, we don't index the file itself # If the file is archived or trashed, we don't index the file itself
if self.theProject.projTree.isTrashRoot(theItem.itemParent): 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 return False
if theRoot.itemClass == nwItemClass.ARCHIVE: if theRoot.itemClass == nwItemClass.ARCHIVE:
logger.info("Not indexing archived item '%s'", tHandle) logger.debug("Not indexing archived item '%s'", tHandle)
return False return False
itemClass = theItem.itemClass itemClass = theItem.itemClass
+2 -2
View File
@@ -251,7 +251,7 @@ class NWItem():
if isinstance(expState, str): if isinstance(expState, str):
self.isExpanded = (expState == str(True)) self.isExpanded = (expState == str(True))
else: else:
self.isExpanded = (expState == True) # noqa: E712 self.isExpanded = (expState is True)
return return
def setExported(self, expState): def setExported(self, expState):
@@ -260,7 +260,7 @@ class NWItem():
if isinstance(expState, str): if isinstance(expState, str):
self.isExported = (expState == str(True)) self.isExported = (expState == str(True))
else: else:
self.isExported = (expState == True) # noqa: E712 self.isExported = (expState is True)
return return
## ##
+3 -2
View File
@@ -369,7 +369,7 @@ class NWProject():
self.clearProject() self.clearProject()
self.projPath = os.path.abspath(os.path.dirname(fileName)) 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 # Standard Folders and Files
# ========================== # ==========================
@@ -611,7 +611,7 @@ class NWProject():
if not self.ensureFolderStructure(): if not self.ensureFolderStructure():
return False return False
logger.debug("Saving project: %s", self.projPath) logger.info("Saving project: %s", self.projPath)
if autoSave: if autoSave:
self.autoCount += 1 self.autoCount += 1
@@ -712,6 +712,7 @@ class NWProject():
def closeProject(self, idleTime=0): def closeProject(self, idleTime=0):
"""Close the current project and clear all meta data. """Close the current project and clear all meta data.
""" """
logger.info("Closing project: %s", self.projPath)
self.optState.saveSettings() self.optState.saveSettings()
self.projTree.writeToCFile() self.projTree.writeToCFile()
self._appendSessionStats(idleTime) self._appendSessionStats(idleTime)
+28 -46
View File
@@ -27,7 +27,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import nw import nw
import os import os
import logging import logging
import configparser
from math import ceil from math import ceil
from functools import partial from functools import partial
@@ -39,6 +38,7 @@ from PyQt5.QtGui import (
) )
from nw.enum import nwAlert from nw.enum import nwAlert
from nw.common import NWConfigParser
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -280,7 +280,7 @@ class GuiTheme:
return False return False
# Config File # Config File
confParser = configparser.ConfigParser() confParser = NWConfigParser()
try: try:
with open(self.confFile, mode="r", encoding="utf-8") as inFile: with open(self.confFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
@@ -292,13 +292,13 @@ class GuiTheme:
# Main # Main
cnfSec = "Main" cnfSec = "Main"
if confParser.has_section(cnfSec): if confParser.has_section(cnfSec):
self.themeName = self._parseLine(confParser, cnfSec, "name", "") self.themeName = confParser.rdStr(cnfSec, "name", "")
self.themeDescription = self._parseLine(confParser, cnfSec, "description", "N/A") self.themeDescription = confParser.rdStr(cnfSec, "description", "N/A")
self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A") self.themeAuthor = confParser.rdStr(cnfSec, "author", "N/A")
self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A") self.themeCredit = confParser.rdStr(cnfSec, "credit", "N/A")
self.themeUrl = self._parseLine(confParser, cnfSec, "url", "") self.themeUrl = confParser.rdStr(cnfSec, "url", "")
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A") self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "") self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
# Palette # Palette
cnfSec = "Palette" cnfSec = "Palette"
@@ -338,7 +338,7 @@ class GuiTheme:
""" """
logger.debug("Loading syntax theme files") logger.debug("Loading syntax theme files")
confParser = configparser.ConfigParser() confParser = NWConfigParser()
try: try:
with open(self.syntaxFile, mode="r", encoding="utf-8") as inFile: with open(self.syntaxFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
@@ -350,13 +350,13 @@ class GuiTheme:
# Main # Main
cnfSec = "Main" cnfSec = "Main"
if confParser.has_section(cnfSec): if confParser.has_section(cnfSec):
self.syntaxName = self._parseLine(confParser, cnfSec, "name", "") self.syntaxName = confParser.rdStr(cnfSec, "name", "")
self.syntaxDescription = self._parseLine(confParser, cnfSec, "description", "") self.syntaxDescription = confParser.rdStr(cnfSec, "description", "")
self.syntaxAuthor = self._parseLine(confParser, cnfSec, "author", "") self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "")
self.syntaxCredit = self._parseLine(confParser, cnfSec, "credit", "") self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "")
self.syntaxUrl = self._parseLine(confParser, cnfSec, "url", "") self.syntaxUrl = confParser.rdStr(cnfSec, "url", "")
self.syntaxLicense = self._parseLine(confParser, cnfSec, "license", "") self.syntaxLicense = confParser.rdStr(cnfSec, "license", "")
self.syntaxLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "") self.syntaxLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
# Syntax # Syntax
cnfSec = "Syntax" cnfSec = "Syntax"
@@ -388,7 +388,7 @@ class GuiTheme:
if self.themeList: if self.themeList:
return self.themeList return self.themeList
confParser = configparser.ConfigParser() confParser = NWConfigParser()
for themeDir in os.listdir(os.path.join(self.mainConf.themeRoot, self.guiPath)): for themeDir in os.listdir(os.path.join(self.mainConf.themeRoot, self.guiPath)):
themeConf = os.path.join( themeConf = os.path.join(
self.mainConf.themeRoot, self.guiPath, themeDir, self.confName self.mainConf.themeRoot, self.guiPath, themeDir, self.confName
@@ -420,7 +420,7 @@ class GuiTheme:
if self.syntaxList: if self.syntaxList:
return self.syntaxList return self.syntaxList
confParser = configparser.ConfigParser() confParser = NWConfigParser()
syntaxDir = os.path.join(self.mainConf.themeRoot, self.syntaxPath) syntaxDir = os.path.join(self.mainConf.themeRoot, self.syntaxPath)
for syntaxFile in os.listdir(syntaxDir): for syntaxFile in os.listdir(syntaxDir):
syntaxPath = os.path.join(syntaxDir, syntaxFile) syntaxPath = os.path.join(syntaxDir, syntaxFile)
@@ -486,15 +486,6 @@ class GuiTheme:
self.guiPalette.setColor(paletteVal, QColor(*readCol)) self.guiPalette.setColor(paletteVal, QColor(*readCol))
return 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 # End Class GuiTheme
@@ -644,7 +635,7 @@ class GuiIcons:
return False return False
# Config File # Config File
confParser = configparser.ConfigParser() confParser = NWConfigParser()
try: try:
with open(self.confFile, mode="r", encoding="utf-8") as inFile: with open(self.confFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
@@ -656,13 +647,13 @@ class GuiIcons:
# Main # Main
cnfSec = "Main" cnfSec = "Main"
if confParser.has_section(cnfSec): if confParser.has_section(cnfSec):
self.themeName = self._parseLine(confParser, cnfSec, "name", "") self.themeName = confParser.rdStr(cnfSec, "name", "")
self.themeDescription = self._parseLine(confParser, cnfSec, "description", "") self.themeDescription = confParser.rdStr(cnfSec, "description", "")
self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A") self.themeAuthor = confParser.rdStr(cnfSec, "author", "N/A")
self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A") self.themeCredit = confParser.rdStr(cnfSec, "credit", "N/A")
self.themeUrl = self._parseLine(confParser, cnfSec, "url", "") self.themeUrl = confParser.rdStr(cnfSec, "url", "")
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A") self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "") self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
# Palette # Palette
cnfSec = "Map" cnfSec = "Map"
@@ -736,7 +727,7 @@ class GuiIcons:
if self.themeList: if self.themeList:
return self.themeList return self.themeList
confParser = configparser.ConfigParser() confParser = NWConfigParser()
for themeDir in os.listdir(self.mainConf.iconPath): for themeDir in os.listdir(self.mainConf.iconPath):
themePath = os.path.join(self.mainConf.iconPath, themeDir) themePath = os.path.join(self.mainConf.iconPath, themeDir)
if not os.path.isdir(themePath) or themeDir == self.fbackName: if not os.path.isdir(themePath) or themeDir == self.fbackName:
@@ -818,13 +809,4 @@ class GuiIcons:
return QIcon() 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 # END Class GuiIcons
+5 -8
View File
@@ -71,12 +71,9 @@ class GuiMain(QMainWindow):
logger.info("OS: %s", self.mainConf.osType) logger.info("OS: %s", self.mainConf.osType)
logger.info("Kernel: %s", self.mainConf.kernelVer) logger.info("Kernel: %s", self.mainConf.kernelVer)
logger.info("Host: %s", self.mainConf.hostName) logger.info("Host: %s", self.mainConf.hostName)
logger.info("Qt5 Version: %s (%d)", logger.info("Qt5: %s (%d)", self.mainConf.verQtString, self.mainConf.verQtValue)
self.mainConf.verQtString, self.mainConf.verQtValue) logger.info("PyQt5: %s (%d)", self.mainConf.verPyQtString, self.mainConf.verPyQtValue)
logger.info("PyQt5 Version: %s (%d)", logger.info("Python: %s (0x%x)", self.mainConf.verPyString, self.mainConf.verPyHexVal)
self.mainConf.verPyQtString, self.mainConf.verPyQtValue)
logger.info("Python Version: %s (0x%x)",
self.mainConf.verPyString, self.mainConf.verPyHexVal)
logger.info("GUI Language: %s", self.mainConf.guiLang) logger.info("GUI Language: %s", self.mainConf.guiLang)
# Core Classes # Core Classes
@@ -297,7 +294,7 @@ class GuiMain(QMainWindow):
logger.debug("Opening project from additional command line option") logger.debug("Opening project from additional command line option")
self.openProject(self.mainConf.cmdOpen) self.openProject(self.mainConf.cmdOpen)
logger.debug("novelWriter is ready ...") logger.info("novelWriter is ready ...")
self.setStatus(self.tr("novelWriter is ready ...")) self.setStatus(self.tr("novelWriter is ready ..."))
return return
@@ -872,7 +869,7 @@ class GuiMain(QMainWindow):
logger.error("No project open") logger.error("No project open")
return False return False
logger.debug("Rebuilding index ...") logger.info("Rebuilding index ...")
qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
tStart = time() tStart = time()
+1 -4
View File
@@ -91,10 +91,7 @@ def fncDir(tmpDir):
shutil.rmtree(fncDir) shutil.rmtree(fncDir)
if not os.path.isdir(fncDir): if not os.path.isdir(fncDir):
os.mkdir(fncDir) os.mkdir(fncDir)
yield fncDir return fncDir
if os.path.isdir(fncDir):
shutil.rmtree(fncDir)
return
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
+168 -57
View File
@@ -19,14 +19,19 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os
import time import time
import pytest import pytest
from datetime import datetime
from tools import writeFile
from nw.common import ( from nw.common import (
checkString, checkBool, checkInt, formatInt, transferCase, checkString, checkBool, checkInt, formatInt, transferCase, fuzzyTime,
fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt, checkHandle, formatTimeStamp, parseTimeStamp, formatTime, hexToInt,
makeFileNameSafe, isHandle, isTitleTag, isItemClass, isItemType, 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("None", 3, True) is None assert checkBool("None", 3, True) is None
assert checkBool("True", False, False) assert checkBool("True", False, False) is True
assert not checkBool("False", True, False) assert checkBool("False", True, False) is False
assert checkBool("Boo", None, False) is None assert checkBool("Boo", None, False) is None
assert not checkBool(0, None, False) assert checkBool(0, None, False) is False
assert checkBool(1, None, False) assert checkBool(1, None, False) is True
assert checkBool(2, None, False) is None assert checkBool(2, None, False) is None
assert checkBool(0.0, None, False) is None assert checkBool(0.0, None, False) is None
assert checkBool(1.0, None, False) is None assert checkBool(1.0, None, False) is None
@@ -96,13 +101,12 @@ def testBaseCommon_CheckHandle():
def testBaseCommon_IsHandle(): def testBaseCommon_IsHandle():
"""Test the isHandle function. """Test the isHandle function.
""" """
assert isHandle("47666c91c7ccf") assert isHandle("47666c91c7ccf") is True
assert isHandle("47666C91C7CCF") is False
assert not isHandle("47666C91C7CCF") assert isHandle("h7666c91c7ccf") is False
assert not isHandle("h7666c91c7ccf") assert isHandle("None") is False
assert not isHandle("None") assert isHandle(None) is False
assert not isHandle(None) assert isHandle("STUFF") is False
assert not isHandle("STUFF")
# END Test testBaseCommon_IsHandle # END Test testBaseCommon_IsHandle
@@ -111,16 +115,16 @@ def testBaseCommon_IsHandle():
def testBaseCommon_IsTitleTag(): def testBaseCommon_IsTitleTag():
"""Test the isItemClass function. """Test the isItemClass function.
""" """
assert isTitleTag("T123456") assert isTitleTag("T123456") is True
assert not isTitleTag("t123456") assert isTitleTag("t123456") is False
assert not isTitleTag("S123456") assert isTitleTag("S123456") is False
assert not isTitleTag("T12345A") assert isTitleTag("T12345A") is False
assert not isTitleTag("T1234567") assert isTitleTag("T1234567") is False
assert not isTitleTag("None") assert isTitleTag("None") is False
assert not isTitleTag(None) assert isTitleTag(None) is False
assert not isTitleTag("STUFF") assert isTitleTag("STUFF") is False
# END Test testBaseCommon_IsTitleTag # END Test testBaseCommon_IsTitleTag
@@ -129,21 +133,21 @@ def testBaseCommon_IsTitleTag():
def testBaseCommon_IsItemClass(): def testBaseCommon_IsItemClass():
"""Test the isItemClass function. """Test the isItemClass function.
""" """
assert isItemClass("NO_CLASS") assert isItemClass("NO_CLASS") is True
assert isItemClass("NOVEL") assert isItemClass("NOVEL") is True
assert isItemClass("PLOT") assert isItemClass("PLOT") is True
assert isItemClass("CHARACTER") assert isItemClass("CHARACTER") is True
assert isItemClass("WORLD") assert isItemClass("WORLD") is True
assert isItemClass("TIMELINE") assert isItemClass("TIMELINE") is True
assert isItemClass("OBJECT") assert isItemClass("OBJECT") is True
assert isItemClass("ENTITY") assert isItemClass("ENTITY") is True
assert isItemClass("CUSTOM") assert isItemClass("CUSTOM") is True
assert isItemClass("ARCHIVE") assert isItemClass("ARCHIVE") is True
assert isItemClass("TRASH") assert isItemClass("TRASH") is True
assert not isItemClass("None") assert isItemClass("None") is False
assert not isItemClass(None) assert isItemClass(None) is False
assert not isItemClass("STUFF") assert isItemClass("STUFF") is False
# END Test testBaseCommon_IsItemClass # END Test testBaseCommon_IsItemClass
@@ -152,15 +156,15 @@ def testBaseCommon_IsItemClass():
def testBaseCommon_IsItemType(): def testBaseCommon_IsItemType():
"""Test the isItemType function. """Test the isItemType function.
""" """
assert isItemType("NO_TYPE") assert isItemType("NO_TYPE") is True
assert isItemType("ROOT") assert isItemType("ROOT") is True
assert isItemType("FOLDER") assert isItemType("FOLDER") is True
assert isItemType("FILE") assert isItemType("FILE") is True
assert isItemType("TRASH") assert isItemType("TRASH") is True
assert not isItemType("None") assert isItemType("None") is False
assert not isItemType(None) assert isItemType(None) is False
assert not isItemType("STUFF") assert isItemType("STUFF") is False
# END Test testBaseCommon_IsItemType # END Test testBaseCommon_IsItemType
@@ -169,19 +173,19 @@ def testBaseCommon_IsItemType():
def testBaseCommon_IsItemLayout(): def testBaseCommon_IsItemLayout():
"""Test the isItemLayout function. """Test the isItemLayout function.
""" """
assert isItemLayout("NO_LAYOUT") assert isItemLayout("NO_LAYOUT") is True
assert isItemLayout("TITLE") assert isItemLayout("TITLE") is True
assert isItemLayout("BOOK") assert isItemLayout("BOOK") is True
assert isItemLayout("PAGE") assert isItemLayout("PAGE") is True
assert isItemLayout("PARTITION") assert isItemLayout("PARTITION") is True
assert isItemLayout("UNNUMBERED") assert isItemLayout("UNNUMBERED") is True
assert isItemLayout("CHAPTER") assert isItemLayout("CHAPTER") is True
assert isItemLayout("SCENE") assert isItemLayout("SCENE") is True
assert isItemLayout("NOTE") assert isItemLayout("NOTE") is True
assert not isItemLayout("None") assert isItemLayout("None") is False
assert not isItemLayout(None) assert isItemLayout(None) is False
assert not isItemLayout("STUFF") assert isItemLayout("STUFF") is False
# END Test testBaseCommon_IsItemLayout # END Test testBaseCommon_IsItemLayout
@@ -234,11 +238,29 @@ def testBaseCommon_FormatTime():
# END Test 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 @pytest.mark.base
def testBaseCommon_FormatInt(): def testBaseCommon_FormatInt():
"""Test the formatInt function. """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(1234) == "1.23\u2009k"
assert formatInt(12345) == "12.3\u2009k" assert formatInt(12345) == "12.3\u2009k"
assert formatInt(123456) == "123\u2009k" assert formatInt(123456) == "123\u2009k"
@@ -247,6 +269,11 @@ def testBaseCommon_FormatInt():
assert formatInt(123456789) == "123\u2009M" assert formatInt(123456789) == "123\u2009M"
assert formatInt(1234567890) == "1.23\u2009G" assert formatInt(1234567890) == "1.23\u2009G"
# Exceptions
assert formatInt(12.3) == "ERR"
assert formatInt(None) == "ERR"
assert formatInt("42") == "ERR"
# END Test testBaseCommon_FormatInt # END Test testBaseCommon_FormatInt
@@ -339,3 +366,87 @@ def testBaseCommon_RomanNumbers():
assert numberToRoman(999, True) == "cmxcix" assert numberToRoman(999, True) == "cmxcix"
# END Test testBaseCommon_RomanNumbers # 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
+55 -69
View File
@@ -19,10 +19,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest
import sys
import os import os
import configparser import sys
import pytest
from shutil import copyfile from shutil import copyfile
@@ -161,6 +160,20 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
assert tstConf.hasError is False assert tstConf.hasError is False
assert tstConf.errData == [] 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.loadConfig()
assert tstConf.saveConfig() assert tstConf.saveConfig()
@@ -187,19 +200,34 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
assert tstConf.saveConfig() assert tstConf.saveConfig()
# Localisation # Localisation
# ============
i18nDir = os.path.join(fncDir, "i18n") i18nDir = os.path.join(fncDir, "i18n")
os.mkdir(i18nDir) os.mkdir(i18nDir)
os.mkdir(os.path.join(i18nDir, "stuff")) os.mkdir(os.path.join(i18nDir, "stuff"))
tstConf.nwLangPath = i18nDir 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_en_GB.ts"), "")
writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "") writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "")
tstApp = MockApp() tstApp = MockApp()
tstConf.initLocalisation(tstApp) tstConf.initLocalisation(tstApp)
# Check Lists
theList = tstConf.listLanguages(tstConf.LANG_NW) theList = tstConf.listLanguages(tstConf.LANG_NW)
assert theList == [("en_GB", "British English")] 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) copyfile(confFile, testFile)
assert cmpFiles(testFile, compFile, [2, 9, 10]) assert cmpFiles(testFile, compFile, [2, 9, 10])
@@ -311,6 +339,7 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
# GUI Scaling # GUI Scaling
# =========== # ===========
tmpConf.guiScale = 1.0 tmpConf.guiScale = 1.0
assert tmpConf.pxInt(10) == 10 assert tmpConf.pxInt(10) == 10
assert tmpConf.pxInt(13) == 13 assert tmpConf.pxInt(13) == 13
@@ -343,6 +372,19 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
assert tmpConf.setWinSize(1200, 650) 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 # Project Tree Columns
tmpConf.guiScale = 2.0 tmpConf.guiScale = 2.0
assert tmpConf.setTreeColWidths([10, 20, 25]) assert tmpConf.setTreeColWidths([10, 20, 25])
@@ -436,6 +478,7 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
# Getters Only # Getters Only
# ============ # ============
tmpConf.guiScale = 1.0 tmpConf.guiScale = 1.0
assert tmpConf.getTextWidth() == 600 assert tmpConf.getTextWidth() == 600
assert tmpConf.getTextMargin() == 40 assert tmpConf.getTextMargin() == 40
@@ -450,6 +493,7 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
# Flag Setters # Flag Setters
# ============ # ============
assert not tmpConf.setShowRefPanel(False) assert not tmpConf.setShowRefPanel(False)
assert not tmpConf.showRefPanel assert not tmpConf.showRefPanel
assert tmpConf.setShowRefPanel(True) assert tmpConf.setShowRefPanel(True)
@@ -482,74 +526,16 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
# Function _packList # Function _packList
assert tmpConf._packList(["A", 1, 2.0, None, False]) == "A, 1, 2.0, None, False" 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 # Function _checkNone
assert tmpConf._checkNone(None) is None assert tmpConf._checkNone(None) is None
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 # Function _checkOptionalPackages
# (Assumes enchant package exists ans is importable) # (Assumes enchant package exists and is importable)
tmpConf._checkOptionalPackages() tmpConf._checkOptionalPackages()
assert tmpConf.hasEnchant is True assert tmpConf.hasEnchant is True
@@ -559,12 +545,12 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
assert tmpConf.hasEnchant is False assert tmpConf.hasEnchant is False
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("shutil.which", lambda *args: "stuff") mp.setattr("shutil.which", lambda *a: "stuff")
tmpConf._checkOptionalPackages() tmpConf._checkOptionalPackages()
assert tmpConf.hasAssistant is True assert tmpConf.hasAssistant is True
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("shutil.which", lambda *args: None) mp.setattr("shutil.which", lambda *a: None)
tmpConf._checkOptionalPackages() tmpConf._checkOptionalPackages()
assert tmpConf.hasAssistant is False assert tmpConf.hasAssistant is False