Merge pull request #447 from vkbo/code_improvements

Code Improvements
This commit is contained in:
Veronica K. Berglyd Olsen
2020-09-18 18:55:55 +02:00
committed by GitHub
17 changed files with 339 additions and 171 deletions
+12
View File
@@ -82,6 +82,18 @@ def checkBool(checkValue, defaultValue, allowNone=False):
return defaultValue return defaultValue
return defaultValue return defaultValue
def checkHandle(checkValue, defaultValue, allowNone=False):
"""Check if a value is a handle.
"""
if allowNone:
if checkValue is None:
return None
if checkValue == "None":
return None
if isHandle(checkValue):
return str(checkValue)
return defaultValue
def isHandle(theString): def isHandle(theString):
"""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!
-2
View File
@@ -4,7 +4,6 @@ from nw.core.document import NWDoc
from nw.core.index import NWIndex from nw.core.index import NWIndex
from nw.core.project import NWProject from nw.core.project import NWProject
from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
from nw.core.tokenizer import Tokenizer
from nw.core.tohtml import ToHtml from nw.core.tohtml import ToHtml
from nw.core.tools import countWords, numberToRoman, numberToWord from nw.core.tools import countWords, numberToRoman, numberToWord
@@ -15,7 +14,6 @@ __all__ = [
"NWSpellCheck", "NWSpellCheck",
"NWSpellEnchant", "NWSpellEnchant",
"NWSpellSimple", "NWSpellSimple",
"Tokenizer",
"ToHtml", "ToHtml",
"countWords", "countWords",
"numberToRoman", "numberToRoman",
+41 -33
View File
@@ -26,11 +26,9 @@
""" """
import logging import logging
import nw
from os import path, rename, unlink from os import path, rename, unlink
from nw.core.item import NWItem
from nw.constants import nwAlert from nw.constants import nwAlert
from nw.common import isHandle from nw.common import isHandle
from nw.constants import nwItemLayout, nwItemClass, nwConst from nw.constants import nwItemLayout, nwItemClass, nwConst
@@ -41,14 +39,14 @@ class NWDoc():
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
self.mainConf = nw.CONFIG
self.theProject = theProject self.theProject = theProject
self.theParent = theParent self.theParent = theParent
self.theItem = None # Internal Variables
self.docHandle = None self._theItem = None # The currently open item
self.fileLoc = None self._docHandle = None # The handle of the currently open item
self.docMeta = "" self._fileLoc = None # The file location of the currently open item
self._docMeta = "" # The meta string of the currently open item
# Internal Mapping # Internal Mapping
self.makeAlert = self.theParent.makeAlert self.makeAlert = self.theParent.makeAlert
@@ -62,10 +60,10 @@ class NWDoc():
def clearDocument(self): def clearDocument(self):
"""Clear the document contents. """Clear the document contents.
""" """
self.theItem = None self._theItem = None
self.docHandle = None self._docHandle = None
self.fileLoc = None self._fileLoc = None
self.docMeta = "" self._docMeta = ""
return return
def openDocument(self, tHandle, showStatus=True, isOrphan=False): def openDocument(self, tHandle, showStatus=True, isOrphan=False):
@@ -78,31 +76,31 @@ class NWDoc():
# Always clear first, since the object will often be reused. # Always clear first, since the object will often be reused.
self.clearDocument() self.clearDocument()
self.docHandle = tHandle self._docHandle = tHandle
if not isOrphan: if not isOrphan:
self.theItem = self.theProject.projTree[tHandle] self._theItem = self.theProject.projTree[tHandle]
else: else:
self.theItem = None self._theItem = None
if self.theItem is None and not isOrphan: if self._theItem is None and not isOrphan:
self.clearDocument() self.clearDocument()
return None return None
docFile = self.docHandle+".nwd" docFile = self._docHandle+".nwd"
logger.debug("Opening document %s" % docFile) logger.debug("Opening document %s" % docFile)
docPath = path.join(self.theProject.projContent, docFile) docPath = path.join(self.theProject.projContent, docFile)
self.fileLoc = docPath self._fileLoc = docPath
theText = "" theText = ""
self.docMeta = "" self._docMeta = ""
if path.isfile(docPath): if path.isfile(docPath):
try: try:
with open(docPath, mode="r", encoding="utf8") as inFile: with open(docPath, mode="r", encoding="utf8") as inFile:
fstLine = inFile.readline() fstLine = inFile.readline()
if fstLine.startswith("%%~ "): if fstLine.startswith("%%~ "):
# This is the meta line # This is the meta line
self.docMeta = fstLine[4:].strip() self._docMeta = fstLine[4:].strip()
else: else:
theText = fstLine theText = fstLine
theText += inFile.read() theText += inFile.read()
@@ -120,10 +118,10 @@ class NWDoc():
logger.debug("The requested document does not exist.") logger.debug("The requested document does not exist.")
return "" return ""
logger.verbose("DocMeta: '%s'" % self.docMeta) logger.verbose("DocMeta: '%s'" % self._docMeta)
if showStatus and not isOrphan: if showStatus and not isOrphan:
self.theParent.statusBar.setStatus("Opened Document: %s" % self.theItem.itemName) self.theParent.statusBar.setStatus("Opened Document: %s" % self._theItem.itemName)
return theText return theText
@@ -131,29 +129,29 @@ class NWDoc():
"""Save the document via temp file in case of save failure, and """Save the document via temp file in case of save failure, and
in any case keep a backup of the file. in any case keep a backup of the file.
""" """
if self.docHandle is None: if self._docHandle is None:
return False return False
self.theProject.ensureFolderStructure() self.theProject.ensureFolderStructure()
docFile = self.docHandle+".nwd" docFile = self._docHandle+".nwd"
logger.debug("Saving document %s" % docFile) logger.debug("Saving document %s" % docFile)
docPath = path.join(self.theProject.projContent, docFile) docPath = path.join(self.theProject.projContent, docFile)
docTemp = path.join(self.theProject.projContent, docFile+"~") docTemp = path.join(self.theProject.projContent, docFile+"~")
if isinstance(self.theItem, NWItem): if self._theItem is None:
itemPath = self.theProject.projTree.getItemPath(self.docHandle) docMeta = ""
else:
itemPath = self.theProject.projTree.getItemPath(self._docHandle)
docMeta = ( docMeta = (
"%%~ {handlepath:s}:{itemclass:s}:{itemlayout:s}:{itemname:s}\n" "%%~ {handlepath:s}:{itemclass:s}:{itemlayout:s}:{itemname:s}\n"
).format( ).format(
handlepath = ":".join(itemPath), handlepath = ":".join(itemPath),
itemclass = self.theItem.itemClass.name, itemclass = self._theItem.itemClass.name,
itemlayout = self.theItem.itemLayout.name, itemlayout = self._theItem.itemLayout.name,
itemname = self.theItem.itemName, itemname = self._theItem.itemName,
) )
else:
docMeta = ""
try: try:
with open(docTemp, mode="w", encoding="utf8") as outFile: with open(docTemp, mode="w", encoding="utf8") as outFile:
@@ -169,7 +167,7 @@ class NWDoc():
unlink(docPath) unlink(docPath)
rename(docTemp, docPath) rename(docTemp, docPath)
self.theParent.statusBar.setStatus("Saved Document: %s" % self.theItem.itemName) self.theParent.statusBar.setStatus("Saved Document: %s" % self._theItem.itemName)
return True return True
@@ -201,15 +199,25 @@ class NWDoc():
# Getters # Getters
## ##
def getFileLocation(self):
"""Return the file location of the current file.
"""
return self._fileLoc
def getCurrentItem(self):
"""Return a pointer to the currently open item.
"""
return self._theItem
def getMeta(self): def getMeta(self):
"""Parses the document meta tag and returns the path and name as """Parses the document meta tag and returns the path and name as
a list and a string. a list and a string.
""" """
if len(self.docMeta) < 14: if len(self._docMeta) < 14:
# Not enough information # Not enough information
return "", [], None, None return "", [], None, None
theMeta = self.docMeta theMeta = self._docMeta
# Scan for handles # Scan for handles
thePath = [] thePath = []
+2 -1
View File
@@ -27,6 +27,7 @@
import logging import logging
import json import json
import nw
from os import path from os import path
from time import time from time import time
@@ -66,9 +67,9 @@ class NWIndex():
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
# Internal # Internal
self.mainConf = nw.CONFIG
self.theProject = theProject self.theProject = theProject
self.theParent = theParent self.theParent = theParent
self.mainConf = self.theParent.mainConf
self.indexBroken = False self.indexBroken = False
# Indices # Indices
-3
View File
@@ -28,7 +28,6 @@
import logging import logging
import json import json
import nw
from os import path from os import path
@@ -40,9 +39,7 @@ class OptionState():
def __init__(self, theProject): def __init__(self, theProject):
self.mainConf = nw.CONFIG
self.theProject = theProject self.theProject = theProject
self.theState = {} self.theState = {}
self.validMap = { self.validMap = {
"GuiWritingStats": { "GuiWritingStats": {
+27 -25
View File
@@ -481,7 +481,7 @@ class NWProject():
# Changes: # Changes:
# 1.0 : Original file format. # 1.0 : Original file format.
# 1.1 : Changes the way documents are structure in the project # 1.1 : Changes the way documents are structured in the project
# folder from data_X, where X is the first hex value of # folder from data_X, where X is the first hex value of
# the handle, to a single content folder. # the handle, to a single content folder.
# 1.2 : Changes the way autoReplace entries are stored. The 1.1 # 1.2 : Changes the way autoReplace entries are stored. The 1.1
@@ -518,15 +518,16 @@ class NWProject():
msgRes = msgBox.question(self.theParent, "Version Conflict", ( msgRes = msgBox.question(self.theParent, "Version Conflict", (
"This project was saved by a newer version of novelWriter, version %s. " "This project was saved by a newer version of novelWriter, version %s. "
"This is version %s. If you continue to open the project, some attributes " "This is version %s. If you continue to open the project, some attributes "
"and settings may not be preserved. Continue opening the project?" "and settings may not be preserved, but the overall project should be fine. "
"Continue opening the project?"
) % ( ) % (
appVersion, nw.__version__ appVersion, nw.__version__
)) ))
if msgRes != QMessageBox.Yes: if msgRes != QMessageBox.Yes:
return False return False
# Start Parsing XML # Start Parsing the XML
# ================= # =====================
for xChild in xRoot: for xChild in xRoot:
if xChild.tag == "project": if xChild.tag == "project":
@@ -578,9 +579,9 @@ class NWProject():
elif xItem.tag == "notesWordCount": elif xItem.tag == "notesWordCount":
self.notesWCount = checkInt(xItem.text, 0, False) self.notesWCount = checkInt(xItem.text, 0, False)
elif xItem.tag == "status": elif xItem.tag == "status":
self.statusItems.unpackEntries(xItem) self.statusItems.unpackXML(xItem)
elif xItem.tag == "importance": elif xItem.tag == "importance":
self.importItems.unpackEntries(xItem) self.importItems.unpackXML(xItem)
elif xItem.tag == "autoReplace": elif xItem.tag == "autoReplace":
for xEntry in xItem: for xEntry in xItem:
if xEntry.tag == "entry": if xEntry.tag == "entry":
@@ -684,9 +685,9 @@ class NWProject():
self._packProjectValue(xTitleFmt, aKey, aValue) self._packProjectValue(xTitleFmt, aKey, aValue)
xStatus = etree.SubElement(xSettings, "status") xStatus = etree.SubElement(xSettings, "status")
self.statusItems.packEntries(xStatus) self.statusItems.packXML(xStatus)
xStatus = etree.SubElement(xSettings, "importance") xStatus = etree.SubElement(xSettings, "importance")
self.importItems.packEntries(xStatus) self.importItems.packXML(xStatus)
# Save Tree Content # Save Tree Content
logger.debug("Writing project content") logger.debug("Writing project content")
@@ -1440,23 +1441,24 @@ class NWProject():
def _deprecatedFiles(self): def _deprecatedFiles(self):
"""Delete files that are no longer used by novelWriter. """Delete files that are no longer used by novelWriter.
""" """
rmList = [] rmList = [
rmList.append(path.join(self.projCache, "nwProject.nwx.0")) path.join(self.projCache, "nwProject.nwx.0"),
rmList.append(path.join(self.projCache, "nwProject.nwx.1")) path.join(self.projCache, "nwProject.nwx.1"),
rmList.append(path.join(self.projCache, "nwProject.nwx.2")) path.join(self.projCache, "nwProject.nwx.2"),
rmList.append(path.join(self.projCache, "nwProject.nwx.3")) path.join(self.projCache, "nwProject.nwx.3"),
rmList.append(path.join(self.projCache, "nwProject.nwx.4")) path.join(self.projCache, "nwProject.nwx.4"),
rmList.append(path.join(self.projCache, "nwProject.nwx.5")) path.join(self.projCache, "nwProject.nwx.5"),
rmList.append(path.join(self.projCache, "nwProject.nwx.6")) path.join(self.projCache, "nwProject.nwx.6"),
rmList.append(path.join(self.projCache, "nwProject.nwx.7")) path.join(self.projCache, "nwProject.nwx.7"),
rmList.append(path.join(self.projCache, "nwProject.nwx.8")) path.join(self.projCache, "nwProject.nwx.8"),
rmList.append(path.join(self.projCache, "nwProject.nwx.9")) path.join(self.projCache, "nwProject.nwx.9"),
rmList.append(path.join(self.projMeta, "mainOptions.json")) path.join(self.projMeta, "mainOptions.json"),
rmList.append(path.join(self.projMeta, "exportOptions.json")) path.join(self.projMeta, "exportOptions.json"),
rmList.append(path.join(self.projMeta, "outlineOptions.json")) path.join(self.projMeta, "outlineOptions.json"),
rmList.append(path.join(self.projMeta, "timelineOptions.json")) path.join(self.projMeta, "timelineOptions.json"),
rmList.append(path.join(self.projMeta, "docMergeOptions.json")) path.join(self.projMeta, "docMergeOptions.json"),
rmList.append(path.join(self.projMeta, "sessionLogOptions.json")) path.join(self.projMeta, "sessionLogOptions.json"),
]
for rmFile in rmList: for rmFile in rmList:
if path.isfile(rmFile): if path.isfile(rmFile):
+2 -5
View File
@@ -89,13 +89,10 @@ class NWSpellCheck():
@staticmethod @staticmethod
def expandLanguage(spTag): def expandLanguage(spTag):
"""Translate a language tag to something more suer friendly. """Translate a language tag to something more user friendly.
""" """
spBits = spTag.split("_") spBits = spTag.split("_")
if spBits[0] in isoLanguage.ISO_639_1: spLang = isoLanguage.ISO_639_1.get(spBits[0], spBits[0])
spLang = isoLanguage.ISO_639_1[spBits[0]]
else:
spLang = spBits[0]
if len(spBits) > 1: if len(spBits) > 1:
spLang += " (%s)" % spBits[1] spLang += " (%s)" % spBits[1]
return spLang return spLang
+42 -42
View File
@@ -36,12 +36,12 @@ logger = logging.getLogger(__name__)
class NWStatus(): class NWStatus():
def __init__(self): def __init__(self):
self.theLabels = [] self._theLabels = []
self.theColours = [] self._theColours = []
self.theCounts = [] self._theCounts = []
self.theMap = {} self._theMap = {}
self.theLength = 0 self._theLength = 0
self.theIndex = 0 self._theIndex = 0
return return
def addEntry(self, theLabel, theColours): def addEntry(self, theLabel, theColours):
@@ -50,11 +50,11 @@ class NWStatus():
""" """
theLabel = theLabel.strip() theLabel = theLabel.strip()
if self.lookupEntry(theLabel) is None: if self.lookupEntry(theLabel) is None:
self.theLabels.append(theLabel) self._theLabels.append(theLabel)
self.theColours.append(theColours) self._theColours.append(theColours)
self.theCounts.append(0) self._theCounts.append(0)
self.theMap[theLabel] = self.theLength self._theMap[theLabel] = self._theLength
self.theLength += 1 self._theLength += 1
return True return True
def lookupEntry(self, theLabel): def lookupEntry(self, theLabel):
@@ -64,8 +64,8 @@ class NWStatus():
if theLabel is None: if theLabel is None:
return None return None
theLabel = theLabel.strip() theLabel = theLabel.strip()
if theLabel in self.theMap.keys(): if theLabel in self._theMap.keys():
return self.theMap[theLabel] return self._theMap[theLabel]
return None return None
def checkEntry(self, theStatus): def checkEntry(self, theStatus):
@@ -77,8 +77,8 @@ class NWStatus():
if self.lookupEntry(theStatus) is not None: if self.lookupEntry(theStatus) is not None:
return theStatus return theStatus
theStatus = checkInt(theStatus, 0, False) theStatus = checkInt(theStatus, 0, False)
if theStatus >= 0 and theStatus < self.theLength: if theStatus >= 0 and theStatus < self._theLength:
return self.theLabels[theStatus] return self._theLabels[theStatus]
def setNewEntries(self, newList): def setNewEntries(self, newList):
"""Update the list of entries after they have been modified by """Update the list of entries after they have been modified by
@@ -87,12 +87,12 @@ class NWStatus():
replaceMap = {} replaceMap = {}
if newList is not None: if newList is not None:
self.theLabels = [] self._theLabels = []
self.theColours = [] self._theColours = []
self.theCounts = [] self._theCounts = []
self.theMap = {} self._theMap = {}
self.theLength = 0 self._theLength = 0
self.theIndex = 0 self._theIndex = 0
for nName, nR, nG, nB, oName in newList: for nName, nR, nG, nB, oName in newList:
self.addEntry(nName, (nR, nG, nB)) self.addEntry(nName, (nR, nG, nB))
@@ -104,7 +104,7 @@ class NWStatus():
def resetCounts(self): def resetCounts(self):
"""Clear the counts of references to the status entries. """Clear the counts of references to the status entries.
""" """
self.theCounts = [0]*self.theLength self._theCounts = [0]*self._theLength
return return
def countEntry(self, theLabel): def countEntry(self, theLabel):
@@ -112,23 +112,23 @@ class NWStatus():
""" """
theIndex = self.lookupEntry(theLabel) theIndex = self.lookupEntry(theLabel)
if theIndex is not None: if theIndex is not None:
self.theCounts[theIndex] += 1 self._theCounts[theIndex] += 1
return return
def packEntries(self, xParent): def packXML(self, xParent):
"""Pack the status entries into an XML object for saving to the """Pack the status entries into an XML object for saving to the
main project file. main project file.
""" """
for n in range(self.theLength): for n in range(self._theLength):
xSub = etree.SubElement(xParent, "entry", attrib={ xSub = etree.SubElement(xParent, "entry", attrib={
"blue" : str(self.theColours[n][2]), "blue" : str(self._theColours[n][2]),
"green" : str(self.theColours[n][1]), "green" : str(self._theColours[n][1]),
"red" : str(self.theColours[n][0]), "red" : str(self._theColours[n][0]),
}) })
xSub.text = self.theLabels[n] xSub.text = self._theLabels[n]
return True return True
def unpackEntries(self, xParent): def unpackXML(self, xParent):
"""Unpack an XML tree and set the class values. """Unpack an XML tree and set the class values.
""" """
theLabels = [] theLabels = []
@@ -151,12 +151,12 @@ class NWStatus():
theColours.append((cR, cG, cB)) theColours.append((cR, cG, cB))
if len(theLabels) > 0: if len(theLabels) > 0:
self.theLabels = [] self._theLabels = []
self.theColours = [] self._theColours = []
self.theCounts = [] self._theCounts = []
self.theMap = {} self._theMap = {}
self.theLength = 0 self._theLength = 0
self.theIndex = 0 self._theIndex = 0
for n in range(len(theLabels)): for n in range(len(theLabels)):
self.addEntry(theLabels[n], theColours[n]) self.addEntry(theLabels[n], theColours[n])
@@ -170,22 +170,22 @@ class NWStatus():
def __getitem__(self, n): def __getitem__(self, n):
"""Return an entry by its index. """Return an entry by its index.
""" """
if n >= 0 and n < self.theLength: if n >= 0 and n < self._theLength:
return self.theLabels[n], self.theColours[n], self.theCounts[n] return self._theLabels[n], self._theColours[n], self._theCounts[n]
return None, None, None return None, None, None
def __iter__(self): def __iter__(self):
"""Initialise the iterator. """Initialise the iterator.
""" """
self.theIndex = 0 self._theIndex = 0
return self return self
def __next__(self): def __next__(self):
"""Return the next entry for the iterator. """Return the next entry for the iterator.
""" """
if self.theIndex < self.theLength: if self._theIndex < self._theLength:
theLabel, theColour, theCount = self.__getitem__(self.theIndex) theLabel, theColour, theCount = self.__getitem__(self._theIndex)
self.theIndex += 1 self._theIndex += 1
return theLabel, theColour, theCount return theLabel, theColour, theCount
else: else:
raise StopIteration raise StopIteration
+1 -2
View File
@@ -309,8 +309,7 @@ class ToHtml(Tokenizer):
def _formatKeywords(self, tText): def _formatKeywords(self, tText):
"""Apply HTML formatting to keywords. """Apply HTML formatting to keywords.
""" """
tText = "@"+tText isValid, theBits, thePos = self.theParent.theIndex.scanThis("@"+tText)
isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText)
if not isValid or not theBits: if not isValid or not theBits:
return "" return ""
+2 -4
View File
@@ -27,7 +27,6 @@
import logging import logging
import re import re
import nw
from operator import itemgetter from operator import itemgetter
from PyQt5.QtCore import QRegularExpression from PyQt5.QtCore import QRegularExpression
@@ -74,9 +73,8 @@ class Tokenizer():
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
self.mainConf = nw.CONFIG self.theProject = theProject
self.theProject = theProject self.theParent = theParent
self.theParent = theParent
# Data Variables # Data Variables
self.theText = None # The raw text to be tokenized self.theText = None # The raw text to be tokenized
+24 -25
View File
@@ -34,7 +34,7 @@ from hashlib import sha256
from time import time from time import time
from nw.core.item import NWItem from nw.core.item import NWItem
from nw.common import checkString from nw.common import checkHandle
from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout, nwConst from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout, nwConst
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -49,7 +49,6 @@ class NWTree():
self._treeOrder = [] # The order of the tree items on the tree view self._treeOrder = [] # The order of the tree items on the tree view
self._treeRoots = [] # The root items of the tree self._treeRoots = [] # The root items of the tree
self._trashRoot = None # The handle of the trash root folder self._trashRoot = None # The handle of the trash root folder
self._theLength = 0 # Always the length of _treeOrder
self._theIndex = 0 # The current iterator index self._theIndex = 0 # The current iterator index
self._treeChanged = False # True if tree structure has changed self._treeChanged = False # True if tree structure has changed
self._handleSeed = None # Used for generating handles for testing self._handleSeed = None # Used for generating handles for testing
@@ -68,7 +67,6 @@ class NWTree():
self._treeRoots = [] self._treeRoots = []
self._trashRoot = None self._trashRoot = None
self._archRoot = None self._archRoot = None
self._theLength = 0
self._theIndex = 0 self._theIndex = 0
self._treeChanged = False self._treeChanged = False
return return
@@ -81,12 +79,12 @@ class NWTree():
def append(self, tHandle, pHandle, nwItem): def append(self, tHandle, pHandle, nwItem):
"""Add a new item to the end of the tree. """Add a new item to the end of the tree.
""" """
tHandle = checkString(tHandle, None, True) tHandle = checkHandle(tHandle, None, True)
pHandle = checkString(pHandle, None, True) pHandle = checkHandle(pHandle, None, True)
if tHandle is None: if tHandle is None:
tHandle = self._makeHandle() tHandle = self._makeHandle()
logger.verbose("Adding entry %s with parent %s" % (str(tHandle), str(pHandle))) logger.verbose("Adding item %s with parent %s" % (str(tHandle), str(pHandle)))
nwItem.setHandle(tHandle) nwItem.setHandle(tHandle)
nwItem.setParent(pHandle) nwItem.setParent(pHandle)
@@ -95,29 +93,29 @@ class NWTree():
self._treeOrder.append(tHandle) self._treeOrder.append(tHandle)
if nwItem.itemType == nwItemType.ROOT: if nwItem.itemType == nwItemType.ROOT:
logger.verbose("Entry %s is a root item" % str(tHandle)) logger.verbose("Item %s is a root item" % str(tHandle))
self._treeRoots.append(tHandle) self._treeRoots.append(tHandle)
if nwItem.itemClass == nwItemClass.ARCHIVE: if nwItem.itemClass == nwItemClass.ARCHIVE:
logger.verbose("Entry %s is the archive folder" % str(tHandle)) logger.verbose("Item %s is the archive folder" % str(tHandle))
self._archRoot = tHandle self._archRoot = tHandle
if nwItem.itemType == nwItemType.TRASH: if nwItem.itemType == nwItemType.TRASH:
if self._trashRoot is None: if self._trashRoot is None:
logger.verbose("Entry %s is the trash folder" % str(tHandle)) logger.verbose("Item %s is the trash folder" % str(tHandle))
self._trashRoot = tHandle self._trashRoot = tHandle
else: else:
logger.error("Only one trash folder allowed") logger.error("Only one trash folder allowed")
self._theLength = len(self._treeOrder)
self._setTreeChanged(True) self._setTreeChanged(True)
return return
def packXML(self, xParent): def packXML(self, xParent):
"""Pack the content of the tree into an XML object. """Pack the content of the tree into the provided XML object. In
the order defined by the _treeOrder list.
""" """
xContent = etree.SubElement(xParent, "content", attrib={ xContent = etree.SubElement(xParent, "content", attrib={
"count": str(self._theLength)} "count": str(len(self._treeOrder))}
) )
for tHandle in self._treeOrder: for tHandle in self._treeOrder:
tItem = self.__getitem__(tHandle) tItem = self.__getitem__(tHandle)
@@ -309,7 +307,6 @@ class NWTree():
# Save the temp list # Save the temp list
self._treeOrder = tmpOrder self._treeOrder = tmpOrder
self._theLength = len(self._treeOrder)
self._setTreeChanged(True) self._setTreeChanged(True)
logger.verbose("Project tree order updated") logger.verbose("Project tree order updated")
@@ -330,7 +327,7 @@ class NWTree():
if tItem is None: if tItem is None:
return False return False
if tItem.itemType != nwItemType.FILE: if tItem.itemType != nwItemType.FILE:
logger.error("Item '%s' is not a file" % tHandle) logger.error("Item %s is not a file" % tHandle)
return False return False
if not isinstance(itemLayout, nwItemLayout): if not isinstance(itemLayout, nwItemLayout):
return False return False
@@ -370,12 +367,12 @@ class NWTree():
def __len__(self): def __len__(self):
"""Return the length counter. Does not check that it is correct! """Return the length counter. Does not check that it is correct!
""" """
return self._theLength return len(self._treeOrder)
def __bool__(self): def __bool__(self):
"""Returns True if the tree has any entries. """Returns True if the tree has any entries.
""" """
return self._theLength > 0 return len(self._treeOrder) > 0
## ##
# Item Access Methods # Item Access Methods
@@ -391,19 +388,21 @@ class NWTree():
return None return None
def __delitem__(self, tHandle): def __delitem__(self, tHandle):
"""This only removes the item from the order list, but not from """Remove an item from the internal lists and dictionaries.
the project tree.
""" """
if tHandle not in self._treeOrder: if tHandle in self._treeOrder and tHandle in self._projTree:
logger.warning( self._treeOrder.remove(tHandle)
"Could not remove item %s from project tree as it does not exist" % tHandle del self._projTree[tHandle]
) else:
logger.warning("Failed to delete item %s: item not found" % tHandle)
return False return False
self._treeOrder.remove(tHandle)
self._theLength = len(self._treeOrder)
if tHandle in self._treeRoots: if tHandle in self._treeRoots:
self._treeRoots.remove(tHandle) self._treeRoots.remove(tHandle)
if tHandle == self._trashRoot:
self._trashRoot = None
if tHandle == self._archRoot:
self._archRoot = None
self._setTreeChanged(True) self._setTreeChanged(True)
@@ -427,7 +426,7 @@ class NWTree():
def __next__(self): def __next__(self):
"""Returns the item from the next entry in the _treeOrder list. """Returns the item from the next entry in the _treeOrder list.
""" """
if self._theIndex < self._theLength: if self._theIndex < len(self._treeOrder):
theItem = self.__getitem__(self._treeOrder[self._theIndex]) theItem = self.__getitem__(self._treeOrder[self._theIndex])
self._theIndex += 1 self._theIndex += 1
return theItem return theItem
+17 -15
View File
@@ -270,10 +270,12 @@ class GuiDocEditor(QTextEdit):
afTime = time() afTime = time()
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime))) logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
if tLine is None: theItem = self.nwDocument.getCurrentItem()
self.setCursorPosition(self.nwDocument.theItem.cursorPos) if tLine is None and theItem is not None:
self.setCursorPosition(theItem.cursorPos)
else: else:
self.setCursorLine(tLine) self.setCursorLine(tLine)
self.lastEdit = time() self.lastEdit = time()
self._runCounter() self._runCounter()
self.wcTimer.start() self.wcTimer.start()
@@ -308,21 +310,20 @@ class GuiDocEditor(QTextEdit):
"""Save the text currently in the editor to the NWDoc object, """Save the text currently in the editor to the NWDoc object,
and update the NWItem meta data. and update the NWItem meta data.
""" """
if self.nwDocument.theItem is None: theItem = self.nwDocument.getCurrentItem()
if theItem is None:
return False return False
docText = self.getText() docText = self.getText()
cursPos = self.getCursorPosition() cursPos = self.getCursorPosition()
self.nwDocument.theItem.setCharCount(self.charCount) theItem.setCharCount(self.charCount)
self.nwDocument.theItem.setWordCount(self.wordCount) theItem.setWordCount(self.wordCount)
self.nwDocument.theItem.setParaCount(self.paraCount) theItem.setParaCount(self.paraCount)
self.nwDocument.theItem.setCursorPos(cursPos) theItem.setCursorPos(cursPos)
self.nwDocument.saveDocument(docText) self.nwDocument.saveDocument(docText)
self.setDocumentChanged(False) self.setDocumentChanged(False)
self.theParent.theIndex.scanText( self.theParent.theIndex.scanText(theItem.itemHandle, docText)
self.nwDocument.theItem.itemHandle, docText
)
return True return True
@@ -598,7 +599,7 @@ class GuiDocEditor(QTextEdit):
"Location: {fileLoc:s}" "Location: {fileLoc:s}"
).format( ).format(
handle = self.theHandle, handle = self.theHandle,
fileLoc = str(self.nwDocument.fileLoc) fileLoc = str(self.nwDocument.getFileLocation())
)) ))
return return
@@ -873,7 +874,8 @@ class GuiDocEditor(QTextEdit):
def _updateCounts(self): def _updateCounts(self):
"""Slot for the word counter's finished signal """Slot for the word counter's finished signal
""" """
if self.theHandle is None or self.nwDocument.theItem is None: theItem = self.nwDocument.getCurrentItem()
if self.theHandle is None or theItem is None:
return return
logger.verbose("Updating word count") logger.verbose("Updating word count")
@@ -881,9 +883,9 @@ class GuiDocEditor(QTextEdit):
self.charCount = self.wCounter.charCount self.charCount = self.wCounter.charCount
self.wordCount = self.wCounter.wordCount self.wordCount = self.wCounter.wordCount
self.paraCount = self.wCounter.paraCount self.paraCount = self.wCounter.paraCount
self.nwDocument.theItem.setCharCount(self.charCount) theItem.setCharCount(self.charCount)
self.nwDocument.theItem.setWordCount(self.wordCount) theItem.setWordCount(self.wordCount)
self.nwDocument.theItem.setParaCount(self.paraCount) theItem.setParaCount(self.paraCount)
self.theParent.treeView.propagateCount(self.theHandle, self.wordCount) self.theParent.treeView.propagateCount(self.theHandle, self.wordCount)
self.theParent.treeView.projectWordCount() self.theParent.treeView.projectWordCount()
+7 -4
View File
@@ -639,7 +639,8 @@ class GuiDocViewHistory():
return return
def _truncateHistory(self, atPos): def _truncateHistory(self, atPos):
"""Truncate the navigation history to the given position. """Truncate the navigation history to the given position. Also
enforces a maximum length of the navigation history to 20.
""" """
nSkip = 1 if atPos > 19 else 0 nSkip = 1 if atPos > 19 else 0
@@ -652,13 +653,15 @@ class GuiDocViewHistory():
return return
def _dumpHistory(self): def _dumpHistory(self):
"""Debug function to dump history. Since it is a for loop, it is """Debug function to dump history to the logger. Since it is a
skipped entirely if log level isn't VERBOSE. for loop, it is skipped entirely if log level isn't VERBOSE.
""" """
if logger.getEffectiveLevel() < logging.DEBUG: if logger.getEffectiveLevel() < logging.DEBUG:
for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory)): for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory)):
logger.verbose( logger.verbose(
"History: %s %2d %13s %5d" % (">" if i == self._currPos else " ", i, h, p) "History %02d: %s %13s [x:%d]" % (
i + 1, ">" if i == self._currPos else " ", h, p
)
) )
return return
+7 -7
View File
@@ -378,11 +378,11 @@ class GuiProjectTree(QTreeWidget):
return True return True
def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False): def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False):
"""Delete items from the tree. Note that this does not delete """Delete an item from the project tree. As a first step, files are
the item from the item tree in the project object. However, moved to the Trash folder. Permanent deletion is a second step. This
since this is only meta data, there isn't really a need to do second step also deletes the item from the project object as well as
that to save memory. Items not in the tree are not saved to the delete the files on disk. Folders are deleted if they're empty only,
project file, so a loaded project will be clean anyway. and the deletion is always permanent.
""" """
if tHandle is None: if tHandle is None:
tHandle = self.getSelectedHandle() tHandle = self.getSelectedHandle()
@@ -584,7 +584,7 @@ class GuiProjectTree(QTreeWidget):
always make sure items with a parent have had their parent item always make sure items with a parent have had their parent item
sent first. sent first.
""" """
logger.debug("Building project tree ...") logger.debug("Building the project tree ...")
self.clear() self.clear()
iCount = 0 iCount = 0
@@ -592,7 +592,7 @@ class GuiProjectTree(QTreeWidget):
iCount += 1 iCount += 1
self._addTreeItem(nwItem) self._addTreeItem(nwItem)
logger.debug("%d items added to project tree" % iCount) logger.debug("%d items added to the project tree" % iCount)
return True return True
def getSelectedHandle(self): def getSelectedHandle(self):
+10 -1
View File
@@ -5,7 +5,7 @@
import pytest import pytest
from nw.common import ( from nw.common import (
checkString, checkBool, checkInt, colRange, formatInt, transferCase, checkString, checkBool, checkInt, colRange, formatInt, transferCase,
fuzzyTime fuzzyTime, checkHandle
) )
from nwtools import cmpList from nwtools import cmpList
@@ -42,6 +42,15 @@ def testCheckBool():
assert checkBool(1.0, None, False) is None assert checkBool(1.0, None, False) is None
assert checkBool(2.0, None, False) is None assert checkBool(2.0, None, False) is None
@pytest.mark.core
def testCheckHandle():
assert checkHandle("None", 1, True) is None
assert checkHandle("None", 1, False) == 1
assert checkHandle(None, 1, True) is None
assert checkHandle(None, 1, False) == 1
assert checkHandle("47666c91c7ccf", None, False) == "47666c91c7ccf"
assert checkHandle("h7666c91c7ccf", None, False) is None
@pytest.mark.core @pytest.mark.core
def testColRange(): def testColRange():
assert colRange([0, 0], [0, 0], 0) is None assert colRange([0, 0], [0, 0], 0) is None
+61 -2
View File
@@ -12,7 +12,7 @@ from nw.core.project import NWProject
from nw.core.document import NWDoc from nw.core.document import NWDoc
from nw.core.index import NWIndex from nw.core.index import NWIndex
from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple
from nw.constants import nwItemClass, nwItemLayout from nw.constants import nwItemClass, nwItemLayout, nwFiles
@pytest.mark.project @pytest.mark.project
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy): def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
@@ -316,7 +316,7 @@ def testDocMeta(nwDummy, nwLipsum):
assert theClass == nwItemClass.NOVEL assert theClass == nwItemClass.NOVEL
assert theLayout == nwItemLayout.SCENE assert theLayout == nwItemLayout.SCENE
aDoc.docMeta = "too_short" aDoc._docMeta = "too_short"
theMeta, thePath, theClass, theLayout = aDoc.getMeta() theMeta, thePath, theClass, theLayout = aDoc.getMeta()
assert theMeta == "" assert theMeta == ""
assert thePath == [] assert thePath == []
@@ -372,3 +372,62 @@ def testSpellSimple(nwTemp, nwConf):
dList = spChk.listDictionaries() dList = spChk.listDictionaries()
assert len(dList) > 0 assert len(dList) > 0
@pytest.mark.project
def testProjectOptions(nwDummy, nwLipsum):
theProject = NWProject(nwDummy)
assert theProject.projMeta is None
theOpts = theProject.optState
assert not theOpts.loadSettings()
assert not theOpts.saveSettings()
# No Settings
assert theProject.openProject(nwLipsum)
assert theOpts.loadSettings()
assert theOpts.saveSettings()
assert str(theOpts.theState) == r"{}"
# Read Invalid Settings and Filter
stateFile = path.join(theProject.projMeta, nwFiles.OPTS_FILE)
with open(stateFile, mode="w", encoding="utf8") as outFile:
outFile.write(
r'{"GuiProjectSettings": {"winWidth": 100, "winHeight": 50}, "NoGroup": {"NoName": 0}}'
)
assert theOpts.loadSettings()
assert str(theOpts.theState) == r"{'GuiProjectSettings': {'winWidth': 100, 'winHeight': 50}}"
# Set New Settings
assert not theOpts.setValue("NoGroup", "NoName", None)
assert not theOpts.setValue("GuiProjectSettings", "NoName", None)
assert theOpts.setValue("GuiProjectSettings", "winWidth", 200)
assert theOpts.setValue("GuiProjectSettings", "winHeight", 80)
assert str(theOpts.theState) == r"{'GuiProjectSettings': {'winWidth': 200, 'winHeight': 80}}"
# Check Read/Write Types
## String
assert theOpts.setValue("GuiWritingStats", "winWidth", "123")
assert isinstance(theOpts.getString("GuiWritingStats", "winWidth", "456"), str)
assert theOpts.getString("GuiWritingStats", "NoName", "456") == "456"
## Int
assert theOpts.setValue("GuiWritingStats", "winWidth", "123")
assert isinstance(theOpts.getInt("GuiWritingStats", "winWidth", 456), int)
assert theOpts.getInt("GuiWritingStats", "NoName", 456) == 456
assert theOpts.setValue("GuiWritingStats", "winWidth", "True")
assert theOpts.getInt("GuiWritingStats", "NoName", 456) == 456
## Float
assert theOpts.setValue("GuiWritingStats", "winWidth", "123")
assert isinstance(theOpts.getFloat("GuiWritingStats", "winWidth", 456.0), float)
assert theOpts.getFloat("GuiWritingStats", "NoName", 456.0) == 456.0
assert theOpts.setValue("GuiWritingStats", "winWidth", "True")
assert theOpts.getFloat("GuiWritingStats", "winWidth", 456.0) == 456.0
## Bool
assert theOpts.setValue("GuiWritingStats", "winWidth", True)
assert isinstance(theOpts.getBool("GuiWritingStats", "winWidth", False), bool)
assert theOpts.getFloat("GuiWritingStats", "NoName", False) is False
assert theOpts.setValue("GuiWritingStats", "winWidth", "True")
assert theOpts.getFloat("GuiWritingStats", "winWidth", False) is False
+84
View File
@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
"""novelWriter Tools Tester
"""
import pytest
from nw.core.tools import countWords, numberToRoman, numberToWord
@pytest.mark.core
def testCountWords():
testText = (
"# Heading One\n"
"## Heading Two\n"
"### Heading Three\n"
"#### Heading Four\n"
"\n"
"@tag: value\n"
"\n"
"% A comment that should n ot be counted.\n"
"\n"
"The first paragraph.\n"
"\n"
"The second paragraph.\n"
"\n"
"\n"
"The third paragraph.\n"
)
cC, wC, pC = countWords(testText)
assert cC == 108
assert wC == 17
assert pC == 3
@pytest.mark.core
def testNumberWords():
assert numberToWord(0, "en") == "Zero"
assert numberToWord(1, "en") == "One"
assert numberToWord(2, "en") == "Two"
assert numberToWord(3, "en") == "Three"
assert numberToWord(4, "en") == "Four"
assert numberToWord(5, "en") == "Five"
assert numberToWord(6, "en") == "Six"
assert numberToWord(7, "en") == "Seven"
assert numberToWord(8, "en") == "Eight"
assert numberToWord(9, "en") == "Nine"
assert numberToWord(10, "en") == "Ten"
assert numberToWord(11, "en") == "Eleven"
assert numberToWord(12, "en") == "Twelve"
assert numberToWord(13, "en") == "Thirteen"
assert numberToWord(14, "en") == "Fourteen"
assert numberToWord(15, "en") == "Fifteen"
assert numberToWord(16, "en") == "Sixteen"
assert numberToWord(17, "en") == "Seventeen"
assert numberToWord(18, "en") == "Eighteen"
assert numberToWord(19, "en") == "Nineteen"
assert numberToWord(20, "en") == "Twenty"
assert numberToWord(21, "en") == "Twenty-One"
assert numberToWord(29, "en") == "Twenty-Nine"
assert numberToWord(42, "en") == "Forty-Two"
assert numberToWord(142, "en") == "One Hundred Forty-Two"
assert numberToWord(999, "en") == "Nine Hundred Ninety-Nine"
@pytest.mark.core
def testRomanNumbers():
assert numberToRoman(None, False) == "NAN"
assert numberToRoman(0, False) == "OOR"
assert numberToRoman(1, False) == "I"
assert numberToRoman(2, False) == "II"
assert numberToRoman(3, False) == "III"
assert numberToRoman(4, False) == "IV"
assert numberToRoman(5, False) == "V"
assert numberToRoman(6, False) == "VI"
assert numberToRoman(7, False) == "VII"
assert numberToRoman(8, False) == "VIII"
assert numberToRoman(9, False) == "IX"
assert numberToRoman(10, False) == "X"
assert numberToRoman(14, False) == "XIV"
assert numberToRoman(42, False) == "XLII"
assert numberToRoman(99, False) == "XCIX"
assert numberToRoman(142, False) == "CXLII"
assert numberToRoman(542, False) == "DXLII"
assert numberToRoman(999, False) == "CMXCIX"
assert numberToRoman(2010, False) == "MMX"
assert numberToRoman(999, True) == "cmxcix"