Merge branch 'main' into release_1.0rc1
This commit is contained in:
+2
-2
@@ -165,9 +165,9 @@ def formatTimeStamp(theTime, fileSafe=False):
|
|||||||
it to a timestamp string.
|
it to a timestamp string.
|
||||||
"""
|
"""
|
||||||
if fileSafe:
|
if fileSafe:
|
||||||
return datetime.fromtimestamp(theTime).strftime(nwConst.fStampFmt)
|
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_FSTAMP)
|
||||||
else:
|
else:
|
||||||
return datetime.fromtimestamp(theTime).strftime(nwConst.tStampFmt)
|
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_TSTAMP)
|
||||||
|
|
||||||
def formatTime(tS):
|
def formatTime(tS):
|
||||||
"""Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format
|
"""Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format
|
||||||
|
|||||||
+7
-7
@@ -37,7 +37,7 @@ from shutil import which
|
|||||||
from PyQt5.Qt import PYQT_VERSION_STR
|
from PyQt5.Qt import PYQT_VERSION_STR
|
||||||
from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
|
from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
|
||||||
|
|
||||||
from nw.constants import nwFiles, nwUnicode
|
from nw.constants import nwConst, nwFiles, nwUnicode
|
||||||
from nw.common import splitVersionNumber, formatTimeStamp
|
from nw.common import splitVersionNumber, formatTimeStamp
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -88,14 +88,14 @@ class Config:
|
|||||||
self.guiIcons = "typicons_colour_light"
|
self.guiIcons = "typicons_colour_light"
|
||||||
self.guiDark = False # Load icons for dark backgrounds, if available
|
self.guiDark = False # Load icons for dark backgrounds, if available
|
||||||
self.guiLang = "en" # Hardcoded for now since the GUI is only in English
|
self.guiLang = "en" # Hardcoded for now since the GUI is only in English
|
||||||
self.guiFont = "" # Defaults to system defualt font
|
self.guiFont = "" # Defaults to system default font
|
||||||
self.guiFontSize = 11
|
self.guiFontSize = 11
|
||||||
self.guiScale = 1.0 # Set automatically by Theme class
|
self.guiScale = 1.0 # Set automatically by Theme class
|
||||||
|
|
||||||
## Sizes
|
## Sizes
|
||||||
self.winGeometry = [1100, 650]
|
self.winGeometry = [1200, 650]
|
||||||
self.treeColWidth = [120, 30, 50]
|
self.treeColWidth = [200, 50, 30]
|
||||||
self.projColWidth = [140, 55, 140]
|
self.projColWidth = [200, 60, 140]
|
||||||
self.mainPanePos = [300, 800]
|
self.mainPanePos = [300, 800]
|
||||||
self.docPanePos = [400, 400]
|
self.docPanePos = [400, 400]
|
||||||
self.viewPanePos = [500, 150]
|
self.viewPanePos = [500, 150]
|
||||||
@@ -284,7 +284,7 @@ class Config:
|
|||||||
logger.verbose("App path: %s" % self.appPath)
|
logger.verbose("App path: %s" % self.appPath)
|
||||||
logger.verbose("Last path: %s" % self.lastPath)
|
logger.verbose("Last path: %s" % self.lastPath)
|
||||||
|
|
||||||
# If config folder does not exist, make it.
|
# If config folder does not exist, create it.
|
||||||
# This assumes that the os config folder itself exists.
|
# This assumes that the os config folder itself exists.
|
||||||
if not os.path.isdir(self.confPath):
|
if not os.path.isdir(self.confPath):
|
||||||
try:
|
try:
|
||||||
@@ -327,7 +327,7 @@ class Config:
|
|||||||
self._checkOptionalPackages()
|
self._checkOptionalPackages()
|
||||||
|
|
||||||
if self.spellTool is None:
|
if self.spellTool is None:
|
||||||
self.spellTool = "internal"
|
self.spellTool = nwConst.SP_INTERNAL
|
||||||
if self.spellLanguage is None:
|
if self.spellLanguage is None:
|
||||||
self.spellLanguage = "en"
|
self.spellLanguage = "en"
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,19 @@ from nw.constants.enum import nwItemClass, nwItemLayout, nwOutline
|
|||||||
|
|
||||||
class nwConst():
|
class nwConst():
|
||||||
|
|
||||||
tStampFmt = "%Y-%m-%d %H:%M:%S" # Default format
|
# Date and Time Formats
|
||||||
fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format
|
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
|
||||||
dStampFmt = "%Y-%m-%d" # Date only format
|
FMT_FSTAMP = "%Y-%m-%d %H.%M.%S" # FileName safe format
|
||||||
|
FMT_DSTAMP = "%Y-%m-%d" # Date only format
|
||||||
|
|
||||||
maxDepth = 30 # Maximum folder depth of a project
|
# Various Hard Limits
|
||||||
maxDocSize = 5000000 # Maxium size of a single document
|
MAX_DEPTH = 30 # Maximum folder depth of a project
|
||||||
maxBuildSize = 10000000 # Maxium size of a project build
|
MAX_DOCSIZE = 5000000 # Maxium size of a single document
|
||||||
|
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
|
||||||
|
|
||||||
|
# Spell Check Providers
|
||||||
|
SP_INTERNAL = "internal"
|
||||||
|
SP_ENCHANT = "enchant"
|
||||||
|
|
||||||
# END Class nwConst
|
# END Class nwConst
|
||||||
|
|
||||||
@@ -74,6 +80,24 @@ class nwKeyWords:
|
|||||||
ENTITY_KEY = "@entity"
|
ENTITY_KEY = "@entity"
|
||||||
CUSTOM_KEY = "@custom"
|
CUSTOM_KEY = "@custom"
|
||||||
|
|
||||||
|
# Set of Valid Keys
|
||||||
|
VALID_KEYS = {
|
||||||
|
TAG_KEY, POV_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY,
|
||||||
|
WORLD_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map from Keys to Item Class
|
||||||
|
KEY_CLASS = {
|
||||||
|
CHAR_KEY : nwItemClass.CHARACTER,
|
||||||
|
POV_KEY : nwItemClass.CHARACTER,
|
||||||
|
PLOT_KEY : nwItemClass.PLOT,
|
||||||
|
TIME_KEY : nwItemClass.TIMELINE,
|
||||||
|
WORLD_KEY : nwItemClass.WORLD,
|
||||||
|
OBJECT_KEY : nwItemClass.OBJECT,
|
||||||
|
ENTITY_KEY : nwItemClass.ENTITY,
|
||||||
|
CUSTOM_KEY : nwItemClass.CUSTOM,
|
||||||
|
}
|
||||||
|
|
||||||
# END Class nwKeyWords
|
# END Class nwKeyWords
|
||||||
|
|
||||||
class nwLabels():
|
class nwLabels():
|
||||||
|
|||||||
+1
-1
@@ -251,7 +251,7 @@ class NWDoc():
|
|||||||
self._docMeta["layout"] = nwItemLayout[metaBits[1]]
|
self._docMeta["layout"] = nwItemLayout[metaBits[1]]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.debug("Ignoring meta data: '%s'" % metaLine)
|
logger.debug("Ignoring meta data: '%s'" % metaLine.strip())
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
+3
-25
@@ -42,28 +42,6 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class NWIndex():
|
class NWIndex():
|
||||||
|
|
||||||
VALID_KEYS = {
|
|
||||||
nwKeyWords.TAG_KEY,
|
|
||||||
nwKeyWords.PLOT_KEY,
|
|
||||||
nwKeyWords.POV_KEY,
|
|
||||||
nwKeyWords.CHAR_KEY,
|
|
||||||
nwKeyWords.WORLD_KEY,
|
|
||||||
nwKeyWords.TIME_KEY,
|
|
||||||
nwKeyWords.OBJECT_KEY,
|
|
||||||
nwKeyWords.ENTITY_KEY,
|
|
||||||
nwKeyWords.CUSTOM_KEY
|
|
||||||
}
|
|
||||||
TAG_CLASS = {
|
|
||||||
nwKeyWords.CHAR_KEY : nwItemClass.CHARACTER,
|
|
||||||
nwKeyWords.POV_KEY : nwItemClass.CHARACTER,
|
|
||||||
nwKeyWords.PLOT_KEY : nwItemClass.PLOT,
|
|
||||||
nwKeyWords.TIME_KEY : nwItemClass.TIMELINE,
|
|
||||||
nwKeyWords.WORLD_KEY : nwItemClass.WORLD,
|
|
||||||
nwKeyWords.OBJECT_KEY : nwItemClass.OBJECT,
|
|
||||||
nwKeyWords.ENTITY_KEY : nwItemClass.ENTITY,
|
|
||||||
nwKeyWords.CUSTOM_KEY : nwItemClass.CUSTOM,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, theProject, theParent):
|
def __init__(self, theProject, theParent):
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
@@ -530,7 +508,7 @@ class NWIndex():
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
# Check that the key is valid
|
# Check that the key is valid
|
||||||
isGood[0] = theBits[0] in self.VALID_KEYS
|
isGood[0] = theBits[0] in nwKeyWords.VALID_KEYS
|
||||||
if not isGood[0] or nBits == 1:
|
if not isGood[0] or nBits == 1:
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
@@ -550,7 +528,7 @@ class NWIndex():
|
|||||||
# If we're still here, we better check that the references exist
|
# If we're still here, we better check that the references exist
|
||||||
for n in range(1, nBits):
|
for n in range(1, nBits):
|
||||||
if theBits[n] in self.tagIndex:
|
if theBits[n] in self.tagIndex:
|
||||||
isGood[n] = self.TAG_CLASS[theBits[0]].name == self.tagIndex[theBits[n]][2]
|
isGood[n] = nwKeyWords.KEY_CLASS[theBits[0]].name == self.tagIndex[theBits[n]][2]
|
||||||
|
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
@@ -608,7 +586,7 @@ class NWIndex():
|
|||||||
section. sTitle must be a string.
|
section. sTitle must be a string.
|
||||||
"""
|
"""
|
||||||
theRefs = {}
|
theRefs = {}
|
||||||
for tKey in self.TAG_CLASS:
|
for tKey in nwKeyWords.KEY_CLASS:
|
||||||
theRefs[tKey] = []
|
theRefs[tKey] = []
|
||||||
|
|
||||||
if tHandle not in self.refIndex:
|
if tHandle not in self.refIndex:
|
||||||
|
|||||||
+12
-16
@@ -31,7 +31,7 @@ import os
|
|||||||
|
|
||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
|
|
||||||
from nw.constants import isoLanguage
|
from nw.constants import nwConst, isoLanguage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -41,11 +41,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class NWSpellCheck():
|
class NWSpellCheck():
|
||||||
|
|
||||||
SP_INTERNAL = "internal"
|
|
||||||
SP_ENCHANT = "enchant"
|
|
||||||
|
|
||||||
theDict = None
|
theDict = None
|
||||||
PROJW = []
|
projDict = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
@@ -71,9 +68,9 @@ class NWSpellCheck():
|
|||||||
def addWord(self, newWord):
|
def addWord(self, newWord):
|
||||||
"""Add a word to the project dictionary.
|
"""Add a word to the project dictionary.
|
||||||
"""
|
"""
|
||||||
if self.projectDict is not None and newWord not in self.PROJW:
|
if self.projectDict is not None and newWord not in self.projDict:
|
||||||
newWord = newWord.strip()
|
newWord = newWord.strip()
|
||||||
self.PROJW.append(newWord)
|
self.projDict.append(newWord)
|
||||||
try:
|
try:
|
||||||
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
|
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
|
||||||
outFile.write("%s\n" % newWord)
|
outFile.write("%s\n" % newWord)
|
||||||
@@ -110,7 +107,7 @@ class NWSpellCheck():
|
|||||||
"""Read the content of the project dictionary, and add it to the
|
"""Read the content of the project dictionary, and add it to the
|
||||||
lookup lists.
|
lookup lists.
|
||||||
"""
|
"""
|
||||||
self.PROJW = []
|
self.projDict = []
|
||||||
if projectDict is not None:
|
if projectDict is not None:
|
||||||
self.projectDict = projectDict
|
self.projectDict = projectDict
|
||||||
if not os.path.isfile(projectDict):
|
if not os.path.isfile(projectDict):
|
||||||
@@ -120,9 +117,9 @@ class NWSpellCheck():
|
|||||||
with open(projectDict, mode="r", encoding="utf-8") as wordsFile:
|
with open(projectDict, mode="r", encoding="utf-8") as wordsFile:
|
||||||
for theLine in wordsFile:
|
for theLine in wordsFile:
|
||||||
theLine = theLine.strip()
|
theLine = theLine.strip()
|
||||||
if len(theLine) > 0 and theLine not in self.PROJW:
|
if len(theLine) > 0 and theLine not in self.projDict:
|
||||||
self.PROJW.append(theLine)
|
self.projDict.append(theLine)
|
||||||
logger.debug("Project word list contains %d words" % len(self.PROJW))
|
logger.debug("Project word list contains %d words" % len(self.projDict))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to load project word list")
|
logger.error("Failed to load project word list")
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
@@ -157,7 +154,7 @@ class NWSpellEnchant(NWSpellCheck):
|
|||||||
self.spellLanguage = None
|
self.spellLanguage = None
|
||||||
|
|
||||||
self._readProjectDictionary(projectDict)
|
self._readProjectDictionary(projectDict)
|
||||||
for pWord in self.PROJW:
|
for pWord in self.projDict:
|
||||||
self.theDict.add_to_session(pWord)
|
self.theDict.add_to_session(pWord)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -236,7 +233,6 @@ class NWSpellSimple(NWSpellCheck):
|
|||||||
when no other is available. This method is fairly slow compared to
|
when no other is available. This method is fairly slow compared to
|
||||||
other implementations.
|
other implementations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
WORDS = []
|
WORDS = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -266,7 +262,7 @@ class NWSpellSimple(NWSpellCheck):
|
|||||||
self.spellLanguage = None
|
self.spellLanguage = None
|
||||||
|
|
||||||
self._readProjectDictionary(projectDict)
|
self._readProjectDictionary(projectDict)
|
||||||
for pWord in self.PROJW:
|
for pWord in self.projDict:
|
||||||
if pWord not in self.WORDS:
|
if pWord not in self.WORDS:
|
||||||
self.WORDS.append(pWord)
|
self.WORDS.append(pWord)
|
||||||
|
|
||||||
@@ -324,7 +320,7 @@ class NWSpellSimple(NWSpellCheck):
|
|||||||
if theBits[1] != ".dict":
|
if theBits[1] != ".dict":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
spName = "%s [internal]" % self.expandLanguage(theBits[0])
|
spName = "%s [%s]" % (self.expandLanguage(theBits[0]), nwConst.SP_INTERNAL)
|
||||||
retList.append((theBits[0], spName))
|
retList.append((theBits[0], spName))
|
||||||
|
|
||||||
return retList
|
return retList
|
||||||
@@ -333,6 +329,6 @@ class NWSpellSimple(NWSpellCheck):
|
|||||||
"""Return the tag and provider of the currently loaded
|
"""Return the tag and provider of the currently loaded
|
||||||
dictionary.
|
dictionary.
|
||||||
"""
|
"""
|
||||||
return self.theLang, "internal"
|
return self.theLang, nwConst.SP_INTERNAL
|
||||||
|
|
||||||
# END Class NWSpellSimple
|
# END Class NWSpellSimple
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class Tokenizer():
|
|||||||
self.theText = theDocument.openDocument(theHandle)
|
self.theText = theDocument.openDocument(theHandle)
|
||||||
|
|
||||||
docSize = len(self.theText)
|
docSize = len(self.theText)
|
||||||
if docSize > nwConst.maxDocSize:
|
if docSize > nwConst.MAX_DOCSIZE:
|
||||||
errVal = "Document '%s' is too big (%.2f MB). Skipping." % (
|
errVal = "Document '%s' is too big (%.2f MB). Skipping." % (
|
||||||
self.theItem.itemName, docSize/1.0e6
|
self.theItem.itemName, docSize/1.0e6
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-2
@@ -260,7 +260,7 @@ class NWTree():
|
|||||||
"""
|
"""
|
||||||
tItem = self.__getitem__(tHandle)
|
tItem = self.__getitem__(tHandle)
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
for i in range(nwConst.maxDepth + 1):
|
for i in range(nwConst.MAX_DEPTH + 1):
|
||||||
if tItem.itemParent is None:
|
if tItem.itemParent is None:
|
||||||
return tItem
|
return tItem
|
||||||
else:
|
else:
|
||||||
@@ -278,7 +278,7 @@ class NWTree():
|
|||||||
tItem = self.__getitem__(tHandle)
|
tItem = self.__getitem__(tHandle)
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
tTree.append(tHandle)
|
tTree.append(tHandle)
|
||||||
for i in range(nwConst.maxDepth + 1):
|
for i in range(nwConst.MAX_DEPTH + 1):
|
||||||
if tItem.itemParent is None:
|
if tItem.itemParent is None:
|
||||||
return tTree
|
return tTree
|
||||||
else:
|
else:
|
||||||
|
|||||||
+12
-5
@@ -422,7 +422,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.buttonBox.addWidget(self.btnSave)
|
self.buttonBox.addWidget(self.btnSave)
|
||||||
self.buttonBox.addWidget(self.btnPrint)
|
self.buttonBox.addWidget(self.btnPrint)
|
||||||
self.buttonBox.addWidget(self.btnClose)
|
self.buttonBox.addWidget(self.btnClose)
|
||||||
self.buttonBox.setSpacing(4)
|
self.buttonBox.setSpacing(self.mainConf.pxInt(4))
|
||||||
|
|
||||||
# Assemble GUI
|
# Assemble GUI
|
||||||
# ============
|
# ============
|
||||||
@@ -464,12 +464,13 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||||
|
|
||||||
# Tools and Buttons Layout
|
# Tools and Buttons Layout
|
||||||
|
tSp = self.mainConf.pxInt(8)
|
||||||
self.innerBox = QVBoxLayout()
|
self.innerBox = QVBoxLayout()
|
||||||
self.innerBox.addWidget(self.toolsArea)
|
self.innerBox.addWidget(self.toolsArea)
|
||||||
self.innerBox.addSpacing(8)
|
self.innerBox.addSpacing(tSp)
|
||||||
self.innerBox.addWidget(self.buildProgress)
|
self.innerBox.addWidget(self.buildProgress)
|
||||||
self.innerBox.addWidget(self.buildNovel)
|
self.innerBox.addWidget(self.buildNovel)
|
||||||
self.innerBox.addSpacing(8)
|
self.innerBox.addSpacing(tSp)
|
||||||
self.innerBox.addLayout(self.buttonBox)
|
self.innerBox.addLayout(self.buttonBox)
|
||||||
|
|
||||||
# Tools and Buttons Wrapper Widget
|
# Tools and Buttons Wrapper Widget
|
||||||
@@ -482,6 +483,12 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.mainSplit.addWidget(self.docView)
|
self.mainSplit.addWidget(self.docView)
|
||||||
self.mainSplit.setSizes([boxWidth, docWidth])
|
self.mainSplit.setSizes([boxWidth, docWidth])
|
||||||
|
|
||||||
|
self.idxSettings = self.mainSplit.indexOf(self.innerWidget)
|
||||||
|
self.idxDocument = self.mainSplit.indexOf(self.docView)
|
||||||
|
|
||||||
|
self.mainSplit.setCollapsible(self.idxSettings, False)
|
||||||
|
self.mainSplit.setCollapsible(self.idxDocument, False)
|
||||||
|
|
||||||
# Outer Layout
|
# Outer Layout
|
||||||
self.outerBox = QHBoxLayout()
|
self.outerBox = QHBoxLayout()
|
||||||
self.outerBox.addWidget(self.mainSplit)
|
self.outerBox.addWidget(self.mainSplit)
|
||||||
@@ -508,7 +515,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.docView.setStyleSheet(self.htmlStyle)
|
self.docView.setStyleSheet(self.htmlStyle)
|
||||||
|
|
||||||
htmlSize = sum([len(x) for x in self.htmlText])
|
htmlSize = sum([len(x) for x in self.htmlText])
|
||||||
if htmlSize < nwConst.maxBuildSize:
|
if htmlSize < nwConst.MAX_BUILDSIZE:
|
||||||
qApp.processEvents()
|
qApp.processEvents()
|
||||||
self.docView.setContent(self.htmlText, self.buildTime)
|
self.docView.setContent(self.htmlText, self.buildTime)
|
||||||
else:
|
else:
|
||||||
@@ -649,7 +656,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
else:
|
else:
|
||||||
self.docView.setStyleSheet(self.htmlStyle)
|
self.docView.setStyleSheet(self.htmlStyle)
|
||||||
|
|
||||||
if htmlSize < nwConst.maxBuildSize:
|
if htmlSize < nwConst.MAX_BUILDSIZE:
|
||||||
self.docView.setContent(self.htmlText, self.buildTime)
|
self.docView.setContent(self.htmlText, self.buildTime)
|
||||||
self._enableQtSave(True)
|
self._enableQtSave(True)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+11
-8
@@ -50,7 +50,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QFrame
|
QFrame
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.core import NWDoc, NWSpellCheck, NWSpellSimple, countWords
|
from nw.core import NWDoc, NWSpellSimple, countWords
|
||||||
from nw.gui.dochighlight import GuiDocHighlighter
|
from nw.gui.dochighlight import GuiDocHighlighter
|
||||||
from nw.common import transferCase
|
from nw.common import transferCase
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
@@ -284,12 +284,12 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
docSize = len(theDoc)
|
docSize = len(theDoc)
|
||||||
if docSize > nwConst.maxDocSize:
|
if docSize > nwConst.MAX_DOCSIZE:
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
"The document you are trying to open is too big. "
|
"The document you are trying to open is too big. "
|
||||||
"The document size is %.2f\u202fMB. "
|
"The document size is %.2f\u202fMB. "
|
||||||
"The maximum size allowed is %.2f\u202fMB."
|
"The maximum size allowed is %.2f\u202fMB."
|
||||||
) % (docSize/1.0e6, nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
|
) % (docSize/1.0e6, nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
|
||||||
self.clearEditor()
|
self.clearEditor()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -361,12 +361,12 @@ class GuiDocEditor(QTextEdit):
|
|||||||
text. This also clears undo history.
|
text. This also clears undo history.
|
||||||
"""
|
"""
|
||||||
docSize = len(theText)
|
docSize = len(theText)
|
||||||
if docSize > nwConst.maxDocSize:
|
if docSize > nwConst.MAX_DOCSIZE:
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
"The text you are trying to add is too big. "
|
"The text you are trying to add is too big. "
|
||||||
"The text size is %.2f\u202fMB. "
|
"The text size is %.2f\u202fMB. "
|
||||||
"The maximum size allowed is %.2f\u202fMB."
|
"The maximum size allowed is %.2f\u202fMB."
|
||||||
) % (docSize/1.0e6, nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
|
) % (docSize/1.0e6, nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
@@ -859,11 +859,11 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""
|
"""
|
||||||
self.lastEdit = time()
|
self.lastEdit = time()
|
||||||
self.lastFind = None
|
self.lastFind = None
|
||||||
if self.qDocument.characterCount() > nwConst.maxDocSize:
|
if self.qDocument.characterCount() > nwConst.MAX_DOCSIZE:
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
"The document has grown too big and you cannot add more text to it. "
|
"The document has grown too big and you cannot add more text to it. "
|
||||||
"The maximum size of a single novelWriter document is %.2f\u202fMB."
|
"The maximum size of a single novelWriter document is %.2f\u202fMB."
|
||||||
) % (nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
|
) % (nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
|
||||||
self.undo()
|
self.undo()
|
||||||
return
|
return
|
||||||
if not self.docChanged:
|
if not self.docChanged:
|
||||||
@@ -1004,6 +1004,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""Decide whether to run the word counter, or not due to
|
"""Decide whether to run the word counter, or not due to
|
||||||
inactivity.
|
inactivity.
|
||||||
"""
|
"""
|
||||||
|
if self.theHandle is None:
|
||||||
|
return
|
||||||
|
|
||||||
if self.wCounter.isRunning():
|
if self.wCounter.isRunning():
|
||||||
logger.verbose("Word counter is busy")
|
logger.verbose("Word counter is busy")
|
||||||
return
|
return
|
||||||
@@ -1611,7 +1614,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""Create the spell checking object based on the spellTool
|
"""Create the spell checking object based on the spellTool
|
||||||
setting in config.
|
setting in config.
|
||||||
"""
|
"""
|
||||||
if self.mainConf.spellTool == NWSpellCheck.SP_ENCHANT:
|
if self.mainConf.spellTool == nwConst.SP_ENCHANT:
|
||||||
from nw.core.spellcheck import NWSpellEnchant
|
from nw.core.spellcheck import NWSpellEnchant
|
||||||
self.theDict = NWSpellEnchant()
|
self.theDict = NWSpellEnchant()
|
||||||
else:
|
else:
|
||||||
|
|||||||
+1
-1
@@ -155,7 +155,7 @@ class GuiDocSplit(QDialog):
|
|||||||
|
|
||||||
# Check that another folder can be created
|
# Check that another folder can be created
|
||||||
parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
|
parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
|
||||||
if len(parTree) >= nwConst.maxDepth - 1:
|
if len(parTree) >= nwConst.MAX_DEPTH - 1:
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
"Cannot add new folder for the document split. "
|
"Cannot add new folder for the document split. "
|
||||||
"Maximum folder depth has been reached. "
|
"Maximum folder depth has been reached. "
|
||||||
|
|||||||
@@ -246,6 +246,27 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def clearDetails(self):
|
||||||
|
"""Clear all the data labels.
|
||||||
|
"""
|
||||||
|
self.titleLabel.setText("<b>Title</b>")
|
||||||
|
self.titleValue.setText("")
|
||||||
|
self.fileValue.setText("")
|
||||||
|
self.itemValue.setText("")
|
||||||
|
self.cCValue.setText("")
|
||||||
|
self.wCValue.setText("")
|
||||||
|
self.pCValue.setText("")
|
||||||
|
self.synopValue.setText("")
|
||||||
|
self.povKeyValue.setText("")
|
||||||
|
self.chrKeyValue.setText("")
|
||||||
|
self.pltKeyValue.setText("")
|
||||||
|
self.timKeyValue.setText("")
|
||||||
|
self.wldKeyValue.setText("")
|
||||||
|
self.objKeyValue.setText("")
|
||||||
|
self.entKeyValue.setText("")
|
||||||
|
self.cstKeyValue.setText("")
|
||||||
|
return
|
||||||
|
|
||||||
def showItem(self, tHandle, sTitle):
|
def showItem(self, tHandle, sTitle):
|
||||||
"""Update the content of the tree with the given handle and line
|
"""Update the content of the tree with the given handle and line
|
||||||
number pointing to a header.
|
number pointing to a header.
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog, QuotesDialog
|
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog, QuotesDialog
|
||||||
from nw.core import NWSpellCheck, NWSpellSimple, NWSpellEnchant
|
from nw.core import NWSpellSimple, NWSpellEnchant
|
||||||
|
from nw.constants import nwConst
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -703,11 +704,11 @@ class GuiConfigEditEditingTab(QWidget):
|
|||||||
## Spell Check Provider and Language
|
## Spell Check Provider and Language
|
||||||
self.spellLangList = QComboBox(self)
|
self.spellLangList = QComboBox(self)
|
||||||
self.spellToolList = QComboBox(self)
|
self.spellToolList = QComboBox(self)
|
||||||
self.spellToolList.addItem("Internal (difflib)", NWSpellCheck.SP_INTERNAL)
|
self.spellToolList.addItem("Internal (difflib)", nwConst.SP_INTERNAL)
|
||||||
self.spellToolList.addItem("Spell Enchant (pyenchant)", NWSpellCheck.SP_ENCHANT)
|
self.spellToolList.addItem("Spell Enchant (pyenchant)", nwConst.SP_ENCHANT)
|
||||||
|
|
||||||
theModel = self.spellToolList.model()
|
theModel = self.spellToolList.model()
|
||||||
idEnchant = self.spellToolList.findData(NWSpellCheck.SP_ENCHANT)
|
idEnchant = self.spellToolList.findData(nwConst.SP_ENCHANT)
|
||||||
theModel.item(idEnchant).setEnabled(self.mainConf.hasEnchant)
|
theModel.item(idEnchant).setEnabled(self.mainConf.hasEnchant)
|
||||||
|
|
||||||
self.spellToolList.currentIndexChanged.connect(self._doUpdateSpellTool)
|
self.spellToolList.currentIndexChanged.connect(self._doUpdateSpellTool)
|
||||||
@@ -814,7 +815,7 @@ class GuiConfigEditEditingTab(QWidget):
|
|||||||
preserve the language choice, if the language exists in the
|
preserve the language choice, if the language exists in the
|
||||||
updated list.
|
updated list.
|
||||||
"""
|
"""
|
||||||
if spellTool == NWSpellCheck.SP_ENCHANT:
|
if spellTool == nwConst.SP_ENCHANT:
|
||||||
theDict = NWSpellEnchant()
|
theDict = NWSpellEnchant()
|
||||||
else:
|
else:
|
||||||
theDict = NWSpellSimple()
|
theDict = NWSpellSimple()
|
||||||
|
|||||||
+3
-3
@@ -243,8 +243,8 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
tHandle = self.theProject.newFile("New File", itemClass, pHandle)
|
tHandle = self.theProject.newFile("New File", itemClass, pHandle)
|
||||||
|
|
||||||
elif itemType == nwItemType.FOLDER:
|
elif itemType == nwItemType.FOLDER:
|
||||||
if len(parTree) >= nwConst.maxDepth - 1:
|
if len(parTree) >= nwConst.MAX_DEPTH - 1:
|
||||||
# Folders cannot be deeper than maxDepth - 1, leaving room
|
# Folders cannot be deeper than MAX_DEPTH - 1, leaving room
|
||||||
# for one more level of files.
|
# for one more level of files.
|
||||||
self.makeAlert((
|
self.makeAlert((
|
||||||
"Cannot add new folder to this item. "
|
"Cannot add new folder to this item. "
|
||||||
@@ -579,7 +579,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||||
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
||||||
|
|
||||||
if not nDepth > nwConst.maxDepth + 1 and pHandle != "":
|
if not nDepth > nwConst.MAX_DEPTH + 1 and pHandle != "":
|
||||||
self.propagateCount(pHandle, pCount, nDepth+1)
|
self.propagateCount(pHandle, pCount, nDepth+1)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -428,10 +428,10 @@ class GuiWritingStats(QDialog):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
dStart = datetime.strptime(
|
dStart = datetime.strptime(
|
||||||
"%s %s" % (inData[0], inData[1]), nwConst.tStampFmt
|
"%s %s" % (inData[0], inData[1]), nwConst.FMT_TSTAMP
|
||||||
)
|
)
|
||||||
dEnd = datetime.strptime(
|
dEnd = datetime.strptime(
|
||||||
"%s %s" % (inData[2], inData[3]), nwConst.tStampFmt
|
"%s %s" % (inData[2], inData[3]), nwConst.FMT_TSTAMP
|
||||||
)
|
)
|
||||||
|
|
||||||
tDiff = dEnd - dStart
|
tDiff = dEnd - dStart
|
||||||
@@ -528,9 +528,9 @@ class GuiWritingStats(QDialog):
|
|||||||
isFirst = False
|
isFirst = False
|
||||||
|
|
||||||
if groupByDay:
|
if groupByDay:
|
||||||
sStart = dStart.strftime(nwConst.dStampFmt)
|
sStart = dStart.strftime(nwConst.FMT_DSTAMP)
|
||||||
else:
|
else:
|
||||||
sStart = dStart.strftime(nwConst.tStampFmt)
|
sStart = dStart.strftime(nwConst.FMT_TSTAMP)
|
||||||
|
|
||||||
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes))
|
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes))
|
||||||
listMax = min(max(listMax, dwTotal), histMax)
|
listMax = min(max(listMax, dwTotal), histMax)
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.closeDocument()
|
self.closeDocument()
|
||||||
self.docViewer.clearNavHistory()
|
self.docViewer.clearNavHistory()
|
||||||
self.projView.closeOutline()
|
self.projView.closeOutline()
|
||||||
|
self.projMeta.clearDetails()
|
||||||
self.theProject.closeProject()
|
self.theProject.closeProject()
|
||||||
self.theIndex.clearIndex()
|
self.theIndex.clearIndex()
|
||||||
self.clearGUI()
|
self.clearGUI()
|
||||||
|
|||||||
@@ -5,23 +5,20 @@ The main setup script for novelWeiter.
|
|||||||
It runs the standard setuptool.setup() with all options taken from the
|
It runs the standard setuptool.setup() with all options taken from the
|
||||||
setup.cfg file.
|
setup.cfg file.
|
||||||
|
|
||||||
In addtion, a few speicalised commands are available:
|
In addtion, a few speicalised commands are available. These are
|
||||||
|
described in the help text in the main section.
|
||||||
* sample: Will build a sample.zip file, which is the way the sample project is
|
|
||||||
included into distributable packages.
|
|
||||||
* qthelp: Will build a QtAssistant readable version of the novelWriter
|
|
||||||
documentation. This should also be a part of distributed packages. It allows
|
|
||||||
for reading the help offline. Otherwise, the F1 button redirects to the
|
|
||||||
online documentation only.
|
|
||||||
* launcher: Will attempt to install novelWriter icons, mime type and create a
|
|
||||||
launcher for the application.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
OS_NONE = 0
|
||||||
|
OS_LINUX = 1
|
||||||
|
OS_WIN = 2
|
||||||
|
OS_DARWIN = 3
|
||||||
|
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
# Qt Assistant Documentation Builder
|
# Qt Assistant Documentation Builder
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
@@ -285,6 +282,19 @@ def xdgInstall():
|
|||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
"""Parse command line options and run the commands.
|
||||||
|
"""
|
||||||
|
# Detect OS
|
||||||
|
if sys.platform.startswith("linux"):
|
||||||
|
hostOS = OS_LINUX
|
||||||
|
elif sys.platform.startswith("darwin"):
|
||||||
|
hostOS = OS_DARWIN
|
||||||
|
elif sys.platform.startswith("win32"):
|
||||||
|
hostOS = OS_WIN
|
||||||
|
elif sys.platform.startswith("cygwin"):
|
||||||
|
hostOS = OS_WIN
|
||||||
|
else:
|
||||||
|
hostOS = OS_NONE
|
||||||
|
|
||||||
helpMsg = (
|
helpMsg = (
|
||||||
"\n"
|
"\n"
|
||||||
@@ -292,10 +302,17 @@ if __name__ == "__main__":
|
|||||||
"======================\n"
|
"======================\n"
|
||||||
"This tool provides some additional setup commands for novelWriter.\n"
|
"This tool provides some additional setup commands for novelWriter.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"help Print the help message.\n"
|
"help Print this help message.\n"
|
||||||
"qthelp Build the help documentation for use with the QtAssistant.\n"
|
"qthelp Build the help documentation for use with the Qt Assistant.\n"
|
||||||
|
" Run before install to enable in the the installed version.\n"
|
||||||
"sample Build the sample project as a zip file.\n"
|
"sample Build the sample project as a zip file.\n"
|
||||||
|
" Run before install to enable creating sample projects.\n"
|
||||||
|
"install Installs novelWriter to the system's Python install location.\n"
|
||||||
|
" Run as root or with sudo for system-wide install, or as\n"
|
||||||
|
" user for single user install.\n"
|
||||||
"xdg-install Install launcher and icons for freedesktop systems.\n"
|
"xdg-install Install launcher and icons for freedesktop systems.\n"
|
||||||
|
" Run as root or with sudo for system-wide install, or as\n"
|
||||||
|
" user for single user install.\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
if "help" in sys.argv:
|
if "help" in sys.argv:
|
||||||
@@ -303,6 +320,11 @@ if __name__ == "__main__":
|
|||||||
print(helpMsg)
|
print(helpMsg)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
if "launcher" in sys.argv:
|
||||||
|
sys.argv.remove("launcher")
|
||||||
|
print("The 'launcher' option has been replaced by 'xdg-install'.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if "qthelp" in sys.argv:
|
if "qthelp" in sys.argv:
|
||||||
sys.argv.remove("qthelp")
|
sys.argv.remove("qthelp")
|
||||||
buildQtDocs()
|
buildQtDocs()
|
||||||
@@ -313,11 +335,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
if "xdg-install" in sys.argv:
|
if "xdg-install" in sys.argv:
|
||||||
sys.argv.remove("xdg-install")
|
sys.argv.remove("xdg-install")
|
||||||
if not sys.platform.startswith("win32"):
|
if hostOS == OS_WIN:
|
||||||
xdgInstall()
|
|
||||||
else:
|
|
||||||
print("ERROR: xdg-install cannot be used on Windows")
|
print("ERROR: xdg-install cannot be used on Windows")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
xdgInstall()
|
||||||
|
|
||||||
if len(sys.argv) <= 1:
|
if len(sys.argv) <= 1:
|
||||||
# Nothing more to do
|
# Nothing more to do
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ guifont =
|
|||||||
guifontsize = 11
|
guifontsize = 11
|
||||||
|
|
||||||
[Sizes]
|
[Sizes]
|
||||||
geometry = 1100, 650
|
geometry = 1200, 650
|
||||||
treecols = 120, 30, 50
|
treecols = 200, 50, 30
|
||||||
projcols = 140, 55, 140
|
projcols = 200, 60, 140
|
||||||
mainpane = 300, 800
|
mainpane = 300, 800
|
||||||
docpane = 400, 400
|
docpane = 400, 400
|
||||||
viewpane = 500, 150
|
viewpane = 500, 150
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ def testConfigSetWinSize(tmpConf, nwTemp, nwRef):
|
|||||||
tmpConf.guiScale = 1.0
|
tmpConf.guiScale = 1.0
|
||||||
|
|
||||||
assert tmpConf.confPath == nwTemp
|
assert tmpConf.confPath == nwTemp
|
||||||
assert tmpConf.setWinSize(1105, 655)
|
assert tmpConf.setWinSize(1205, 655)
|
||||||
assert not tmpConf.confChanged
|
assert not tmpConf.confChanged
|
||||||
assert tmpConf.setWinSize(70, 70)
|
assert tmpConf.setWinSize(70, 70)
|
||||||
assert tmpConf.confChanged
|
assert tmpConf.confChanged
|
||||||
assert tmpConf.setWinSize(1100, 650)
|
assert tmpConf.setWinSize(1200, 650)
|
||||||
assert tmpConf.saveConfig()
|
assert tmpConf.saveConfig()
|
||||||
|
|
||||||
assert cmpFiles(testConf, refConf, [2])
|
assert cmpFiles(testConf, refConf, [2])
|
||||||
@@ -62,13 +62,13 @@ def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef):
|
|||||||
assert tmpConf.confPath == nwTemp
|
assert tmpConf.confPath == nwTemp
|
||||||
tmpConf.guiScale = 1.0
|
tmpConf.guiScale = 1.0
|
||||||
|
|
||||||
assert tmpConf.setTreeColWidths([10, 20, 30])
|
assert tmpConf.setTreeColWidths([10, 20, 25])
|
||||||
assert tmpConf.treeColWidth == [10, 20, 30]
|
assert tmpConf.treeColWidth == [10, 20, 25]
|
||||||
assert tmpConf.setTreeColWidths([120, 30, 50])
|
assert tmpConf.setTreeColWidths([200, 50, 30])
|
||||||
|
|
||||||
assert tmpConf.setProjColWidths([10, 20, 30])
|
assert tmpConf.setProjColWidths([10, 20, 30])
|
||||||
assert tmpConf.projColWidth == [10, 20, 30]
|
assert tmpConf.projColWidth == [10, 20, 30]
|
||||||
assert tmpConf.setProjColWidths([140, 55, 140])
|
assert tmpConf.setProjColWidths([200, 60, 140])
|
||||||
|
|
||||||
assert tmpConf.confChanged
|
assert tmpConf.confChanged
|
||||||
assert tmpConf.saveConfig()
|
assert tmpConf.saveConfig()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from nwtools import cmpFiles
|
|||||||
from nw.core.project import NWProject
|
from nw.core.project import NWProject
|
||||||
from nw.core.document import NWDoc
|
from nw.core.document import NWDoc
|
||||||
from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple
|
from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple
|
||||||
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
|
from nw.constants import nwConst, nwItemClass, nwItemType, nwItemLayout, nwFiles
|
||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
|
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
|
||||||
@@ -405,7 +405,7 @@ def testSpellSimple(nwTemp, nwConf):
|
|||||||
|
|
||||||
aTag, aName = spChk.describeDict()
|
aTag, aName = spChk.describeDict()
|
||||||
assert aTag == "en"
|
assert aTag == "en"
|
||||||
assert aName == "internal"
|
assert aName == nwConst.SP_INTERNAL
|
||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testProjectOptions(nwDummy, nwLipsum):
|
def testProjectOptions(nwDummy, nwLipsum):
|
||||||
|
|||||||
Reference in New Issue
Block a user