Use Ruff for linting and fix a ton of issues

This commit is contained in:
Veronica Berglyd Olsen
2025-04-03 01:15:52 +02:00
parent d2796d27c3
commit 53a2d0e691
43 changed files with 257 additions and 235 deletions
+3 -4
View File
@@ -23,11 +23,10 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: pip install -r requirements.txt -r requirements-dev.txt run: pip install -r requirements.txt -r requirements-dev.txt
- name: Run Flake8 - name: Run Ruff
run: | run: |
flake8 --version ruff --version
flake8 novelwriter --count --show-source --statistics ruff check
flake8 tests --count --show-source --statistics --extend-ignore ANN
- name: Run Pyright - name: Run Pyright
run: | run: |
pyright --version pyright --version
+1 -1
View File
@@ -12,7 +12,7 @@ import time
# -- Project Information ----------------------------------------------------- # -- Project Information -----------------------------------------------------
project = "novelWriter" project = "novelWriter"
copyright = f"{datetime.date.today().year}" copyright = f"{datetime.date.today().year}" # noqa: A001
tmp_authors = ["Veronica Berglyd Olsen"] tmp_authors = ["Veronica Berglyd Olsen"]
if additional := os.environ.get("SPHINX_I18N_AUTHORS"): if additional := os.environ.get("SPHINX_I18N_AUTHORS"):
+6 -6
View File
@@ -159,7 +159,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
print(helpMsg) print(helpMsg)
sys.exit(0) sys.exit(0)
elif inOpt in ("-v", "--version"): elif inOpt in ("-v", "--version"):
print("novelWriter Version %s [%s]" % (__version__, __date__)) print(f"novelWriter Version {__version__} [{__date__}]")
sys.exit(0) sys.exit(0)
elif inOpt in ("-i", "--info"): elif inOpt in ("-i", "--info"):
logLevel = logging.INFO logLevel = logging.INFO
@@ -207,17 +207,17 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
errorCode = 0 errorCode = 0
if sys.hexversion < 0x030a00f0: if sys.hexversion < 0x030a00f0:
errorData.append( errorData.append(
"At least Python 3.10 is required, found %s" % CONFIG.verPyString f"At least Python 3.10 is required, found {CONFIG.verPyString}"
) )
errorCode |= 0x04 errorCode |= 0x04
if CONFIG.verQtValue < 0x060400: if CONFIG.verQtValue < 0x060400:
errorData.append( errorData.append(
"At least Qt6 version 6.4 is required, found %s" % CONFIG.verQtString f"At least Qt6 version 6.4 is required, found {CONFIG.verQtString}"
) )
errorCode |= 0x08 errorCode |= 0x08
if CONFIG.verPyQtValue < 0x060400: if CONFIG.verPyQtValue < 0x060400:
errorData.append( errorData.append(
"At least PyQt6 version 6.4 is required, found %s" % CONFIG.verPyQtString f"At least PyQt6 version 6.4 is required, found {CONFIG.verPyQtString}"
) )
errorCode |= 0x10 errorCode |= 0x10
@@ -228,9 +228,9 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
errDlg.showMessage(( errDlg.showMessage((
"<h3>A critical error was encountered</h3>" "<h3>A critical error was encountered</h3>"
"<p>novelWriter cannot start due to the following issues:<p>" "<p>novelWriter cannot start due to the following issues:<p>"
"<p>&nbsp;-&nbsp;%s</p>" "<p>&nbsp;-&nbsp;{0}</p>"
"<p>Shutting down ...</p>" "<p>Shutting down ...</p>"
) % ( ).format(
"<br>&nbsp;-&nbsp;".join(errorData) "<br>&nbsp;-&nbsp;".join(errorData)
)) ))
for errLine in errorData: for errLine in errorData:
+1 -1
View File
@@ -490,7 +490,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
"""Encode a dictionary, list or tuple as a json object or array, and """Encode a dictionary, list or tuple as a json object or array, and
indent from level n up to a max level nmax if nmax is larger than 0. indent from level n up to a max level nmax if nmax is larger than 0.
""" """
if not isinstance(data, (dict, list, tuple)): if not isinstance(data, dict | list | tuple):
return "[]" return "[]"
buffer = [] buffer = []
+4 -4
View File
@@ -355,7 +355,7 @@ class Config:
"""Set the last used path. Only the folder is saved, so if the """Set the last used path. Only the folder is saved, so if the
path is not a folder, the parent of the path is used instead. path is not a folder, the parent of the path is used instead.
""" """
if isinstance(path, (str, Path)): if isinstance(path, str | Path):
path = checkPath(path, self._homePath) path = checkPath(path, self._homePath)
if not path.is_dir(): if not path.is_dir():
path = path.parent path = path.parent
@@ -504,10 +504,10 @@ class Config:
and dataPath is mainly intended for the test suite. and dataPath is mainly intended for the test suite.
""" """
logger.debug("Initialising Config ...") logger.debug("Initialising Config ...")
if isinstance(confPath, (str, Path)): if isinstance(confPath, str | Path):
logger.info("Setting alternative config path: %s", confPath) logger.info("Setting alternative config path: %s", confPath)
self._confPath = Path(confPath) self._confPath = Path(confPath)
if isinstance(dataPath, (str, Path)): if isinstance(dataPath, str | Path):
logger.info("Setting alternative data path: %s", dataPath) logger.info("Setting alternative data path: %s", dataPath)
self._dataPath = Path(dataPath) self._dataPath = Path(dataPath)
@@ -555,7 +555,7 @@ class Config:
for lngPath, lngBase in langList: for lngPath, lngBase in langList:
for lngCode in self._qLocale.uiLanguages(): for lngCode in self._qLocale.uiLanguages():
qTrans = QTranslator() qTrans = QTranslator()
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_")) lngFile = "{0}_{1}".format(lngBase, lngCode.replace("-", "_"))
if lngFile not in self._qtTrans: if lngFile not in self._qtTrans:
if qTrans.load(lngFile, lngPath): if qTrans.load(lngFile, lngPath):
logger.debug("Loaded: %s.qm", lngFile) logger.debug("Loaded: %s.qm", lngFile)
+1 -1
View File
@@ -643,7 +643,7 @@ class nwUnicode:
H_LTRIS = "&#9666;" H_LTRIS = "&#9666;"
class nwHtmlUnicode(): class nwHtmlUnicode:
U_TO_H = { U_TO_H = {
# Quotes # Quotes
+2 -2
View File
@@ -294,12 +294,12 @@ class BuildSettings:
def getInt(self, key: str) -> int: def getInt(self, key: str) -> int:
"""Type safe value access for integers.""" """Type safe value access for integers."""
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1]) value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
return int(value) if isinstance(value, (int, float)) else 0 return int(value) if isinstance(value, int | float) else 0
def getFloat(self, key: str) -> float: def getFloat(self, key: str) -> float:
"""Type safe value access for floats.""" """Type safe value access for floats."""
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1]) value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
return float(value) if isinstance(value, (int, float)) else 0.0 return float(value) if isinstance(value, int | float) else 0.0
## ##
# Setters # Setters
+2 -2
View File
@@ -379,7 +379,7 @@ class ProjectBuilder:
"""Build or copy a project from a data dictionary.""" """Build or copy a project from a data dictionary."""
if isinstance(data, dict): if isinstance(data, dict):
path = data.get("path", None) or None path = data.get("path", None) or None
if isinstance(path, (str, Path)): if isinstance(path, str | Path):
self._path = Path(path).resolve() self._path = Path(path).resolve()
if data.get("sample", False): if data.get("sample", False):
return self._extractSampleProject(self._path) return self._extractSampleProject(self._path)
@@ -509,7 +509,7 @@ class ProjectBuilder:
# Also add the archive and trash folders # Also add the archive and trash folders
project.newRoot(nwItemClass.ARCHIVE) project.newRoot(nwItemClass.ARCHIVE)
project.tree.trash # Triggers the creation of Trash _ = project.tree.trash # Triggers the creation of Trash
project.saveProject() project.saveProject()
project.closeProject() project.closeProject()
+2 -2
View File
@@ -316,13 +316,13 @@ class NWBuildDocument:
bldObj.setStyles(self._build.getBool("html.addStyles")) bldObj.setStyles(self._build.getBool("html.addStyles"))
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode")) bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
if isinstance(bldObj, (ToOdt, ToDocX)): if isinstance(bldObj, ToOdt | ToDocX):
bldObj.setHeaderFormat( bldObj.setHeaderFormat(
self._build.getStr("doc.pageHeader"), self._build.getStr("doc.pageHeader"),
self._build.getInt("doc.pageCountOffset"), self._build.getInt("doc.pageCountOffset"),
) )
if isinstance(bldObj, (ToOdt, ToDocX, ToQTextDocument)): if isinstance(bldObj, ToOdt | ToDocX | ToQTextDocument):
scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0) scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0)) pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
bldObj.setPageLayout( bldObj.setPageLayout(
+2 -1
View File
@@ -333,8 +333,9 @@ class ProjectModel(QAbstractItemModel):
return self.createIndex(parent.row(), 0, parent) return self.createIndex(parent.row(), 0, parent)
return QModelIndex() return QModelIndex()
def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex: def index(self, row: int, column: int, parent: QModelIndex | None = None) -> QModelIndex:
"""Get the index of a child item of a parent.""" """Get the index of a child item of a parent."""
parent = parent or QModelIndex()
if self.hasIndex(row, column, parent): if self.hasIndex(row, column, parent):
node: ProjectNode = parent.internalPointer() if parent.isValid() else self._root node: ProjectNode = parent.internalPointer() if parent.isValid() else self._root
if child := node.child(row): if child := node.child(row):
+1 -1
View File
@@ -691,7 +691,7 @@ class _ReplacePage(NFixedPage):
self.listBox.itemSelectionChanged.connect(self._onSelectionChanged) self.listBox.itemSelectionChanged.connect(self._onSelectionChanged)
for aKey, aVal in SHARED.project.data.autoReplace.items(): for aKey, aVal in SHARED.project.data.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal]) newItem = QTreeWidgetItem([f"<{aKey}>", aVal])
self.listBox.addTopLevelItem(newItem) self.listBox.addTopLevelItem(newItem)
self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder) self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder)
+1 -1
View File
@@ -78,7 +78,7 @@ class GuiQuoteSelect(NDialog):
minSize = 100 minSize = 100
for sKey, sLabel in nwQuotes.SYMBOLS.items(): for sKey, sLabel in nwQuotes.SYMBOLS.items():
text = "[ %s ] %s" % (sKey, trConst(sLabel)) text = f"[ {sKey} ] {trConst(sLabel)}"
minSize = max(minSize, metrics.boundingRect(text).width()) minSize = max(minSize, metrics.boundingRect(text).width())
qtItem = QListWidgetItem(text) qtItem = QListWidgetItem(text)
qtItem.setData(self.D_KEY, sKey) qtItem.setData(self.D_KEY, sKey)
+2 -2
View File
@@ -144,7 +144,7 @@ class NWErrorMessage(QDialog):
try: try:
txtTrace = "\n".join(format_tb(exTrace)) txtTrace = "\n".join(format_tb(exTrace))
self.msgBody.setPlainText(( self.msgBody.setPlainText(
"Environment:\n" "Environment:\n"
f"novelWriter Version: {__version__}\n" f"novelWriter Version: {__version__}\n"
f"Host OS: {sys.platform} ({kernelVersion})\n" f"Host OS: {sys.platform} ({kernelVersion})\n"
@@ -153,7 +153,7 @@ class NWErrorMessage(QDialog):
f"enchant: {enchantVersion}\n\n" f"enchant: {enchantVersion}\n\n"
f"{exType.__name__}:\n{str(exValue)}\n\n" f"{exType.__name__}:\n{str(exValue)}\n\n"
f"Traceback:\n{txtTrace}\n" f"Traceback:\n{txtTrace}\n"
)) )
except Exception: except Exception:
self.msgBody.setPlainText("Failed to generate error report ...") self.msgBody.setPlainText("Failed to generate error report ...")
+1 -1
View File
@@ -48,7 +48,7 @@ class WheelEventFilter(QObject):
self._locked = False self._locked = False
return return
def eventFilter(self, object: QObject, event: QEvent) -> bool: def eventFilter(self, obj: QObject, event: QEvent) -> bool:
"""Filter events of type QWheelEvent and forward them to the """Filter events of type QWheelEvent and forward them to the
parent widget's wheelEvent handler. parent widget's wheelEvent handler.
""" """
+4 -4
View File
@@ -155,15 +155,15 @@ class NDoubleSpinBox(QDoubleSpinBox):
self, self,
parent: QWidget | None = None, parent: QWidget | None = None,
*, *,
min: float = 0.0, minVal: float = 0.0,
max: float = 15.0, maxVal: float = 15.0,
step: float = 0.1, step: float = 0.1,
prec: int = 2, prec: int = 2,
) -> None: ) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setMinimum(min) self.setMinimum(minVal)
self.setMaximum(max) self.setMaximum(maxVal)
self.setSingleStep(step) self.setSingleStep(step)
self.setDecimals(prec) self.setDecimals(prec)
return return
+2 -2
View File
@@ -212,9 +212,9 @@ class ToDocX(Tokenizer):
self._pageMargins = QMargins(_mmToSz(left), _mmToSz(top), _mmToSz(right), _mmToSz(bottom)) self._pageMargins = QMargins(_mmToSz(left), _mmToSz(top), _mmToSz(right), _mmToSz(bottom))
return return
def setHeaderFormat(self, format: str, offset: int) -> None: def setHeaderFormat(self, value: str, offset: int) -> None:
"""Set the document header format.""" """Set the document header format."""
self._headerFormat = format.strip() self._headerFormat = value.strip()
self._pageOffset = offset self._pageOffset = offset
return return
+3 -3
View File
@@ -203,9 +203,9 @@ class ToOdt(Tokenizer):
self._mDocRight = f"{right/10.0:.3f}cm" self._mDocRight = f"{right/10.0:.3f}cm"
return return
def setHeaderFormat(self, format: str, offset: int) -> None: def setHeaderFormat(self, value: str, offset: int) -> None:
"""Set the document header format.""" """Set the document header format."""
self._headerFormat = format.strip() self._headerFormat = value.strip()
self._pageOffset = offset self._pageOffset = offset
return return
@@ -1509,7 +1509,7 @@ class XMLParagraph:
errMsg = "" errMsg = ""
nMissed = len(self._rawTxt) - self._chrPos nMissed = len(self._rawTxt) - self._chrPos
if nMissed != 0: if nMissed != 0:
errMsg = "%d char(s) were not written: '%s'" % (nMissed, self._rawTxt) errMsg = f"{nMissed} char(s) were not written: '{self._rawTxt}'"
return nMissed, errMsg return nMissed, errMsg
## ##
+7 -5
View File
@@ -978,7 +978,7 @@ class GuiDocEditor(QPlainTextEdit):
super().dropEvent(event) super().dropEvent(event)
return return
def focusNextPrevChild(self, next: bool) -> bool: def focusNextPrevChild(self, _next: bool) -> bool:
"""Capture the focus request from the tab key on the text """Capture the focus request from the tab key on the text
editor. If the editor has focus, we do not change focus and editor. If the editor has focus, we do not change focus and
allow the editor to insert a tab. If the search bar has focus, allow the editor to insert a tab. If the search bar has focus,
@@ -1037,7 +1037,7 @@ class GuiDocEditor(QPlainTextEdit):
logger.error("Invalid keyword '%s'", keyword) logger.error("Invalid keyword '%s'", keyword)
return False return False
logger.debug("Inserting keyword '%s'", keyword) logger.debug("Inserting keyword '%s'", keyword)
state = self.insertNewBlock("%s: " % keyword) state = self.insertNewBlock(f"{keyword}: ")
return state return state
@pyqtSlot() @pyqtSlot()
@@ -1528,10 +1528,10 @@ class GuiDocEditor(QPlainTextEdit):
if fLen == min(numA, numB): if fLen == min(numA, numB):
cursor.beginEditBlock() cursor.beginEditBlock()
cursor.setPosition(posS) cursor.setPosition(posS)
for i in range(fLen): for _ in range(fLen):
cursor.deletePreviousChar() cursor.deletePreviousChar()
cursor.setPosition(posE) cursor.setPosition(posE)
for i in range(fLen): for _ in range(fLen):
cursor.deletePreviousChar() cursor.deletePreviousChar()
cursor.endEditBlock() cursor.endEditBlock()
@@ -1943,7 +1943,9 @@ class GuiDocEditor(QPlainTextEdit):
exist = False exist = False
cPos = cursor.selectionStart() - block.position() cPos = cursor.selectionStart() - block.position()
tExist = SHARED.project.index.checkThese(tBits, self._docHandle) tExist = SHARED.project.index.checkThese(tBits, self._docHandle)
for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)): for sTag, sPos, sExist in zip(
reversed(tBits), reversed(tPos), reversed(tExist), strict=False
):
if cPos >= sPos: if cPos >= sPos:
# The cursor is between the start of two tags # The cursor is between the start of two tags
if cPos <= sPos + len(sTag): if cPos <= sPos + len(sTag):
+3 -6
View File
@@ -610,12 +610,9 @@ class GuiDocViewHistory:
for loop, it is skipped entirely if log level isn't DEBUG. for loop, it is skipped entirely if log level isn't DEBUG.
""" """
if CONFIG.isDebug: # pragma: no cover if CONFIG.isDebug: # pragma: no cover
for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory)): for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory, strict=False)):
logger.debug( a = ">" if i == self._currPos else " "
"History %02d: %s %13s [x:%d]" % ( logger.debug(f"History {i + 1:02d}: {a} {h:13s} [x:{p}]")
i + 1, ">" if i == self._currPos else " ", h, p
)
)
return return
+3 -3
View File
@@ -936,7 +936,7 @@ class GuiMainMenu(QMenuBar):
# Search > Find in Project # Search > Find in Project
self.aFindProj = qtAddAction(self.srcMenu, self.tr("Find in Project")) self.aFindProj = qtAddAction(self.srcMenu, self.tr("Find in Project"))
self.aFindProj.setShortcut("Ctrl+Shift+F") self.aFindProj.setShortcut("Ctrl+Shift+F")
self.aFindProj.triggered.connect(lambda: self.requestViewChange.emit(nwView.SEARCH)) self.aFindProj.triggered.connect(qtLambda(self.requestViewChange.emit, nwView.SEARCH))
return return
@@ -956,10 +956,10 @@ class GuiMainMenu(QMenuBar):
self.mSelectLanguage = qtAddMenu(self.toolsMenu, self.tr("Spell Check Language")) self.mSelectLanguage = qtAddMenu(self.toolsMenu, self.tr("Spell Check Language"))
languages = SHARED.spelling.listDictionaries() languages = SHARED.spelling.listDictionaries()
languages.insert(0, ("None", self.tr("Default"))) languages.insert(0, ("None", self.tr("Default")))
for n, (tag, language) in enumerate(languages): for tag, language in languages:
aSpell = QAction(self.mSelectLanguage) aSpell = QAction(self.mSelectLanguage)
aSpell.setText(language) aSpell.setText(language)
aSpell.triggered.connect(lambda n, tag=tag: self._changeSpelling(tag)) aSpell.triggered.connect(qtLambda(self._changeSpelling, tag))
self.mSelectLanguage.addAction(aSpell) self.mSelectLanguage.addAction(aSpell)
# Tools > Re-Run Spell Check # Tools > Re-Run Spell Check
+4 -4
View File
@@ -259,17 +259,17 @@ class GuiProjectToolBar(QWidget):
self.mQuick = QMenu(self) self.mQuick = QMenu(self)
self.tbQuick = NIconToolButton(self, iSz) self.tbQuick = NIconToolButton(self, iSz)
self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links")) self.tbQuick.setToolTip("{0} [Ctrl+L]".format(self.tr("Quick Links")))
self.tbQuick.setShortcut("Ctrl+L") self.tbQuick.setShortcut("Ctrl+L")
self.tbQuick.setMenu(self.mQuick) self.tbQuick.setMenu(self.mQuick)
# Move Buttons # Move Buttons
self.tbMoveU = NIconToolButton(self, iSz) self.tbMoveU = NIconToolButton(self, iSz)
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up")) self.tbMoveU.setToolTip("{0} [Ctrl+Up]".format(self.tr("Move Up")))
self.tbMoveU.clicked.connect(self.projTree.moveItemUp) self.tbMoveU.clicked.connect(self.projTree.moveItemUp)
self.tbMoveD = NIconToolButton(self, iSz) self.tbMoveD = NIconToolButton(self, iSz)
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down")) self.tbMoveD.setToolTip("{0} [Ctrl+Down]".format(self.tr("Move Down")))
self.tbMoveD.clicked.connect(self.projTree.moveItemDown) self.tbMoveD.clicked.connect(self.projTree.moveItemDown)
# Add Item Menu # Add Item Menu
@@ -309,7 +309,7 @@ class GuiProjectToolBar(QWidget):
self._buildRootMenu() self._buildRootMenu()
self.tbAdd = NIconToolButton(self, iSz) self.tbAdd = NIconToolButton(self, iSz)
self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item")) self.tbAdd.setToolTip("{0} [Ctrl+N]".format(self.tr("Add Item")))
self.tbAdd.setShortcut("Ctrl+N") self.tbAdd.setShortcut("Ctrl+N")
self.tbAdd.setMenu(self.mAdd) self.tbAdd.setMenu(self.mAdd)
+1 -1
View File
@@ -155,4 +155,4 @@ class _PopRightMenu(QMenu):
if isinstance(parent := self.parent(), QWidget): if isinstance(parent := self.parent(), QWidget):
offset = QPoint(parent.width(), parent.height() - self.height()) offset = QPoint(parent.width(), parent.height() - self.height())
self.move(parent.mapToGlobal(offset)) self.move(parent.mapToGlobal(offset))
return super(_PopRightMenu, self).event(event) return super().event(event)
+2 -2
View File
@@ -370,7 +370,7 @@ class GuiMain(QMainWindow):
return True return True
if not isYes: if not isYes:
msgYes = SHARED.question("%s<br>%s" % ( msgYes = SHARED.question("{0}<br>{1}".format(
self.tr("Close the current project?"), self.tr("Close the current project?"),
self.tr("Changes are saved automatically.") self.tr("Changes are saved automatically.")
)) ))
@@ -844,7 +844,7 @@ class GuiMain(QMainWindow):
def closeMain(self) -> bool: def closeMain(self) -> bool:
"""Save everything, and close novelWriter.""" """Save everything, and close novelWriter."""
if SHARED.hasProject and CONFIG.askBeforeExit and not SHARED.question("%s<br>%s" % ( if SHARED.hasProject and CONFIG.askBeforeExit and not SHARED.question("{0}<br>{1}".format(
self.tr("Do you want to exit novelWriter?"), self.tr("Do you want to exit novelWriter?"),
self.tr("Changes are saved automatically.") self.tr("Changes are saved automatically.")
)): )):
+4 -4
View File
@@ -101,28 +101,28 @@ class SharedData(QObject):
def mainGui(self) -> GuiMain: def mainGui(self) -> GuiMain:
"""Return the Main GUI instance.""" """Return the Main GUI instance."""
if self._gui is None: if self._gui is None:
raise Exception("SharedData class not fully initialised") raise RuntimeError("SharedData class not fully initialised")
return self._gui return self._gui
@property @property
def theme(self) -> GuiTheme: def theme(self) -> GuiTheme:
"""Return the GUI Theme instance.""" """Return the GUI Theme instance."""
if self._theme is None: if self._theme is None:
raise Exception("SharedData class not fully initialised") raise RuntimeError("SharedData class not fully initialised")
return self._theme return self._theme
@property @property
def project(self) -> NWProject: def project(self) -> NWProject:
"""Return the active NWProject instance.""" """Return the active NWProject instance."""
if self._project is None: if self._project is None:
raise Exception("SharedData class not fully initialised") raise RuntimeError("SharedData class not fully initialised")
return self._project return self._project
@property @property
def spelling(self) -> NWSpellEnchant: def spelling(self) -> NWSpellEnchant:
"""Return the active NWProject instance.""" """Return the active NWProject instance."""
if self._spelling is None: if self._spelling is None:
raise Exception("SharedData class not fully initialised") raise RuntimeError("SharedData class not fully initialised")
return self._spelling return self._spelling
@property @property
+14 -14
View File
@@ -333,7 +333,7 @@ class GuiManuscript(NToolDialog):
def _deleteSelectedBuild(self) -> None: def _deleteSelectedBuild(self) -> None:
"""Delete the currently selected build settings entry.""" """Delete the currently selected build settings entry."""
if build := self._getSelectedBuild(): if build := self._getSelectedBuild():
if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))): if SHARED.question(self.tr("Delete build '{0}'?").format(build.name)):
if dialog := self._findSettingsDialog(build.buildID): if dialog := self._findSettingsDialog(build.buildID):
dialog.close() dialog.close()
self._builds.removeBuild(build.buildID) self._builds.removeBuild(build.buildID)
@@ -977,23 +977,23 @@ class _StatsWidget(QWidget):
def updateStats(self, data: dict[str, int]) -> None: def updateStats(self, data: dict[str, int]) -> None:
"""Update the stats values from a Tokenizer stats dict.""" """Update the stats values from a Tokenizer stats dict."""
# Minimal # Minimal
self.minWordCount.setText("{0:n}".format(data.get(nwStats.WORDS, 0))) self.minWordCount.setText(f"{data.get(nwStats.WORDS, 0):n}")
self.minCharCount.setText("{0:n}".format(data.get(nwStats.CHARS, 0))) self.minCharCount.setText(f"{data.get(nwStats.CHARS, 0):n}")
# Maximal # Maximal
self.maxTotalWords.setText("{0:n}".format(data.get(nwStats.WORDS, 0))) self.maxTotalWords.setText(f"{data.get(nwStats.WORDS, 0):n}")
self.maxHeadWords.setText("{0:n}".format(data.get(nwStats.WORDS_TITLE, 0))) self.maxHeadWords.setText(f"{data.get(nwStats.WORDS_TITLE, 0):n}")
self.maxTextWords.setText("{0:n}".format(data.get(nwStats.WORDS_TEXT, 0))) self.maxTextWords.setText(f"{data.get(nwStats.WORDS_TEXT, 0):n}")
self.maxTitleCount.setText("{0:n}".format(data.get(nwStats.TITLES, 0))) self.maxTitleCount.setText(f"{data.get(nwStats.TITLES, 0):n}")
self.maxParCount.setText("{0:n}".format(data.get(nwStats.PARAGRAPHS, 0))) self.maxParCount.setText(f"{data.get(nwStats.PARAGRAPHS, 0):n}")
self.maxTotalChars.setText("{0:n}".format(data.get(nwStats.CHARS, 0))) self.maxTotalChars.setText(f"{data.get(nwStats.CHARS, 0):n}")
self.maxHeaderChars.setText("{0:n}".format(data.get(nwStats.CHARS_TITLE, 0))) self.maxHeaderChars.setText(f"{data.get(nwStats.CHARS_TITLE, 0):n}")
self.maxTextChars.setText("{0:n}".format(data.get(nwStats.CHARS_TEXT, 0))) self.maxTextChars.setText(f"{data.get(nwStats.CHARS_TEXT, 0):n}")
self.maxTotalWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_ALL, 0))) self.maxTotalWordChars.setText(f"{data.get(nwStats.WCHARS_ALL, 0):n}")
self.maxHeadWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_TITLE, 0))) self.maxHeadWordChars.setText(f"{data.get(nwStats.WCHARS_TITLE, 0):n}")
self.maxTextWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_TEXT, 0))) self.maxTextWordChars.setText(f"{data.get(nwStats.WCHARS_TEXT, 0):n}")
return return
+4 -4
View File
@@ -228,8 +228,8 @@ class GuiBuildSettings(NToolDialog):
""" """
if self._build.changed: if self._build.changed:
response = SHARED.question(self.tr( response = SHARED.question(self.tr(
"Do you want to save your changes to '{0}'?".format(self._build.name) "Do you want to save your changes to '{0}'?"
)) ).format(self._build.name))
if response: if response:
self._emitBuildData() self._emitBuildData()
self._build.resetChangedState() self._build.resetChangedState()
@@ -1168,11 +1168,11 @@ class _FormattingTab(NScrollableForm):
for key, name in nwLabels.PAPER_NAME.items(): for key, name in nwLabels.PAPER_NAME.items():
self.pageSize.addItem(trConst(name), key) self.pageSize.addItem(trConst(name), key)
self.pageWidth = NDoubleSpinBox(self, max=500.0) self.pageWidth = NDoubleSpinBox(self, maxVal=500.0)
self.pageWidth.setFixedWidth(dbW) self.pageWidth.setFixedWidth(dbW)
self.pageWidth.valueChanged.connect(self._pageSizeValueChanged) self.pageWidth.valueChanged.connect(self._pageSizeValueChanged)
self.pageHeight = NDoubleSpinBox(self, max=500.0) self.pageHeight = NDoubleSpinBox(self, maxVal=500.0)
self.pageHeight.setFixedWidth(dbW) self.pageHeight.setFixedWidth(dbW)
self.pageHeight.valueChanged.connect(self._pageSizeValueChanged) self.pageHeight.valueChanged.connect(self._pageSizeValueChanged)
+2 -2
View File
@@ -447,7 +447,7 @@ class GuiWritingStats(NToolDialog):
rType = record.get("type") rType = record.get("type")
if rType == "initial": if rType == "initial":
self.wordOffset = checkInt(record.get("offset"), 0) self.wordOffset = checkInt(record.get("offset"), 0)
logger.debug("Initial word count when log was started is %d" % self.wordOffset) logger.debug("Initial word count when log was started is %d", self.wordOffset)
elif rType == "record": elif rType == "record":
try: try:
dStart = datetime.fromisoformat(str(record.get("start"))) dStart = datetime.fromisoformat(str(record.get("start")))
@@ -570,7 +570,7 @@ class GuiWritingStats(NToolDialog):
idleEntry = formatTime(sIdle) idleEntry = formatTime(sIdle)
else: else:
sRatio = sIdle/sDiff if sDiff > 0.0 else 0.0 sRatio = sIdle/sDiff if sDiff > 0.0 else 0.0
idleEntry = "%d %%" % round(100.0 * sRatio) idleEntry = f"{round(100.0 * sRatio)} %"
newItem = QTreeWidgetItem() newItem = QTreeWidgetItem()
newItem.setText(self.C_TIME, sStart) newItem.setText(self.C_TIME, sStart)
+3 -3
View File
@@ -102,11 +102,11 @@ def cleanBuildDirs(args: argparse.Namespace) -> None:
if folder.is_dir(): if folder.is_dir():
try: try:
shutil.rmtree(folder) shutil.rmtree(folder)
print("Deleted: %s" % folder) print(f"Deleted: {folder}")
except OSError: except OSError:
print("Failed: %s" % folder) print(f"Failed: {folder}")
else: else:
print("Missing: %s" % folder) print(f"Missing: {folder}")
print("") print("")
+32 -5
View File
@@ -55,17 +55,44 @@ force_grid_wrap = 0
lines_between_types = 1 lines_between_types = 1
forced_separate = ["tests.*"] forced_separate = ["tests.*"]
[tool.flake8] [tool.ruff]
max-line-length = 99 line-length = 99
ignore = ["E133", "E221", "E226", "E228", "E241", "W503", "ANN101", "ANN102", "ANN401"]
per-file-ignores = ["tests/*:ANN"] [tool.ruff.lint]
exclude = ["docs/*"] select = [
"A", # flake8-builtins (A)
"B", # flake8-bugbear (B)
"E", # pycodestyle (E)
"F", # Pyflakes (F)
"W", # pycodestyle (W)
"UP",
]
ignore = [
"E221", # multiple-spaces-before-operator
"E226", # missing-whitespace-around-arithmetic-operator
"E228", # missing-whitespace-around-modulo-operator
"E241", # multiple-spaces-after-comma
"ANN401",
"UP015", # redundant-open-modes
"UP030", # format-literals
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = []
[tool.ruff.format]
quote-style = "double"
[tool.pyright] [tool.pyright]
include = ["novelwriter"] include = ["novelwriter"]
exclude = ["**/__pycache__"] exclude = ["**/__pycache__"]
reportIncompatibleMethodOverride = false reportIncompatibleMethodOverride = false
reportGeneralTypeIssues = "information"
reportOptionalMemberAccess = "information"
reportOptionalOperand = "information"
reportOptionalSubscript = "information"
reportUnboundVariable = "error"
pythonVersion = "3.10" pythonVersion = "3.10"
+1 -4
View File
@@ -1,6 +1,3 @@
flake8 ruff
flake8-annotations
flake8-pep585
flake8-pyproject
isort isort
pyright pyright
+1 -2
View File
@@ -195,8 +195,7 @@ def mockRnd(monkeypatch):
self.reset() self.reset()
def _rnd(self, n): def _rnd(self, n):
for x in range(n): yield from range(n)
yield x
def reset(self): def reset(self):
gen = self._rnd(1000) gen = self._rnd(1000)
+8 -8
View File
@@ -41,14 +41,14 @@ def testBaseSharedData_Init():
shared = SharedData() shared = SharedData()
# When not initialised, it should raise exceptions # When not initialised, it should raise exceptions
with pytest.raises(Exception): with pytest.raises(RuntimeError):
shared.mainGui _ = shared.mainGui
with pytest.raises(Exception): with pytest.raises(RuntimeError):
shared.theme _ = shared.theme
with pytest.raises(Exception): with pytest.raises(RuntimeError):
shared.project _ = shared.project
with pytest.raises(Exception): with pytest.raises(RuntimeError):
shared.spelling _ = shared.spelling
# Create some mock objects # Create some mock objects
mockGui = MockGuiMain() mockGui = MockGuiMain()
+3 -3
View File
@@ -885,9 +885,9 @@ def testCoreIndex_ExtractData(nwGUI, fncPath, mockRnd):
sHandle = project.newFile("Scene One", C.hNovelRoot) sHandle = project.newFile("Scene One", C.hNovelRoot)
tHandle = project.newFile("Scene Two", C.hNovelRoot) tHandle = project.newFile("Scene Two", C.hNovelRoot)
project.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore assert project.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore
project.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore assert project.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore
project.tree[tHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore assert project.tree[tHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore
assert index.scanText(hHandle, "## Chapter One\n\n") # type: ignore assert index.scanText(hHandle, "## Chapter One\n\n") # type: ignore
assert index.scanText(sHandle, "### Scene One\n\n") # type: ignore assert index.scanText(sHandle, "### Scene One\n\n") # type: ignore
+22 -22
View File
@@ -74,7 +74,7 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.openDocument(C.hSceneDoc) assert nwGUI.openDocument(C.hSceneDoc)
docEditor = nwGUI.docEditor docEditor = nwGUI.docEditor
docEditor.setPlainText("### Lorem Ipsum\n\n%s" % ipsumText[0]) docEditor.setPlainText(f"### Lorem Ipsum\n\n{ipsumText[0]}")
nwGUI.saveDocument() nwGUI.saveDocument()
# Check Defaults # Check Defaults
@@ -147,7 +147,7 @@ def testGuiEditor_LoadText(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
docEditor = nwGUI.docEditor docEditor = nwGUI.docEditor
longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText*20) longText = "### Lorem Ipsum\n\n{0}".format("\n\n".join(ipsumText*20))
docEditor.replaceText(longText) docEditor.replaceText(longText)
nwGUI.saveDocument() nwGUI.saveDocument()
nwGUI.closeDocument() nwGUI.closeDocument()
@@ -179,7 +179,7 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
docEditor = nwGUI.docEditor docEditor = nwGUI.docEditor
longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText) longText = "### Lorem Ipsum\n\n{0}".format("\n\n".join(ipsumText))
docEditor.replaceText(longText) docEditor.replaceText(longText)
# Missing item # Missing item
@@ -483,7 +483,7 @@ def testGuiEditor_SpellChecking(qtbot, monkeypatch, nwGUI, projPath, ipsumText,
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
docEditor = nwGUI.docEditor docEditor = nwGUI.docEditor
text = "### A Scene\n\n%s" % "\n\n".join(ipsumText) text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText))
docEditor.replaceText(text) docEditor.replaceText(text)
# Toggle State # Toggle State
@@ -584,7 +584,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
docEditor = nwGUI.docEditor docEditor = nwGUI.docEditor
text = "### A Scene\n\n%s" % "\n\n".join(ipsumText) text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText))
docEditor.replaceText(text) docEditor.replaceText(text)
doc = docEditor.document() doc = docEditor.document()
@@ -650,7 +650,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Emphasis/Undo/Redo # Emphasis/Undo/Redo
# ================== # ==================
text = "### A Scene\n\n%s" % ipsumText[0] text = f"### A Scene\n\n{ipsumText[0]}"
docEditor.replaceText(text) docEditor.replaceText(text)
# Emphasis # Emphasis
@@ -683,7 +683,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Shortcodes # Shortcodes
# ========== # ==========
text = "### A Scene\n\n%s" % ipsumText[0] text = f"### A Scene\n\n{ipsumText[0]}"
docEditor.replaceText(text) docEditor.replaceText(text)
# Italic # Italic
@@ -738,7 +738,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Quotes # Quotes
# ====== # ======
text = "### A Scene\n\n%s" % ipsumText[0] text = f"### A Scene\n\n{ipsumText[0]}"
docEditor.replaceText(text) docEditor.replaceText(text)
# Add Single Quotes # Add Single Quotes
@@ -772,7 +772,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Remove Line Breaks # Remove Line Breaks
# ================== # ==================
text = "### A Scene\n\n%s" % ipsumText[0] text = f"### A Scene\n\n{ipsumText[0]}"
repText = text[:100] + text[100:].replace(" ", "\n", 3) repText = text[:100] + text[100:].replace(" ", "\n", 3)
docEditor.replaceText(repText) docEditor.replaceText(repText)
assert docEditor.docAction(nwDocAction.RM_BREAKS) is True assert docEditor.docAction(nwDocAction.RM_BREAKS) is True
@@ -974,7 +974,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
docEditor = nwGUI.docEditor docEditor = nwGUI.docEditor
text = "### A Scene\n\n%s" % ipsumText[0] text = f"### A Scene\n\n{ipsumText[0]}"
# Insert Text # Insert Text
# =========== # ===========
@@ -1037,7 +1037,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
# Insert KeyWords # Insert KeyWords
# =============== # ===============
text = "### A Scene\n\n\n%s" % ipsumText[0] text = f"### A Scene\n\n\n{ipsumText[0]}"
docEditor.replaceText(text) docEditor.replaceText(text)
docEditor.setCursorLine(3) docEditor.setCursorLine(3)
@@ -1076,13 +1076,13 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
docEditor = nwGUI.docEditor docEditor = nwGUI.docEditor
text = "### A Scene\n\n%s" % "\n\n".join(ipsumText) text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText))
docEditor.replaceText(text) docEditor.replaceText(text)
# Wrap Selection # Wrap Selection
# ============== # ==============
text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]) text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText[0:2]))
docEditor.replaceText(text) docEditor.replaceText(text)
docEditor.setCursorPosition(45) docEditor.setCursorPosition(45)
@@ -1114,7 +1114,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Toggle Format # Toggle Format
# ============= # =============
text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]) text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText[0:2]))
# Block format repetition # Block format repetition
docEditor.replaceText(text) docEditor.replaceText(text)
@@ -1186,7 +1186,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# ============== # ==============
# No Selection # No Selection
text = "### A Scene\n\n%s" % ipsumText[0].replace("consectetur", "=consectetur=") text = "### A Scene\n\n{0}".format(ipsumText[0].replace("consectetur", "=consectetur="))
docEditor.replaceText(text) docEditor.replaceText(text)
docEditor.setCursorPosition(45) docEditor.setCursorPosition(45)
docEditor._replaceQuotes("=", "<", ">") docEditor._replaceQuotes("=", "<", ">")
@@ -1194,7 +1194,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# First Paragraph Selected # First Paragraph Selected
# This should not replace anything in second paragraph # This should not replace anything in second paragraph
text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum=") text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum="))
docEditor.replaceText(text) docEditor.replaceText(text)
docEditor.setCursorPosition(45) docEditor.setCursorPosition(45)
assert docEditor.docAction(nwDocAction.SEL_PARA) assert docEditor.docAction(nwDocAction.SEL_PARA)
@@ -1222,7 +1222,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Check Blocks # Check Blocks
cursor = docEditor.textCursor() cursor = docEditor.textCursor()
cursor.clearSelection() cursor.clearSelection()
text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) text = f"### A Scene\n\n{parOne}\n\n{parTwo}"
docEditor.replaceText(text) docEditor.replaceText(text)
docEditor.setCursorPosition(45) docEditor.setCursorPosition(45)
assert len(docEditor._selectedBlocks(cursor)) == 0 assert len(docEditor._selectedBlocks(cursor)) == 0
@@ -1231,15 +1231,15 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert len(docEditor._selectedBlocks(cursor)) == 15 assert len(docEditor._selectedBlocks(cursor)) == 15
# Remove All # Remove All
text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) text = f"### A Scene\n\n{parOne}\n\n{parTwo}"
docEditor.replaceText(text) docEditor.replaceText(text)
docEditor.setCursorPosition(45) docEditor.setCursorPosition(45)
docEditor._removeInParLineBreaks() docEditor._removeInParLineBreaks()
assert docEditor.getText() == "### A Scene\n\n%s\n" % "\n\n".join(ipsumText[0:2]) assert docEditor.getText() == "### A Scene\n\n{0}\n".format("\n\n".join(ipsumText[0:2]))
# Remove in First Paragraph # Remove in First Paragraph
# Second paragraphs should remain unchanged # Second paragraphs should remain unchanged
text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) text = f"### A Scene\n\n{parOne}\n\n{parTwo}"
docEditor.replaceText(text) docEditor.replaceText(text)
cursor = docEditor.textCursor() cursor = docEditor.textCursor()
cursor.setPosition(16, QtMoveAnchor) cursor.setPosition(16, QtMoveAnchor)
@@ -1260,7 +1260,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Key Press Events # Key Press Events
# ================ # ================
text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) text = f"### A Scene\n\n{parOne}\n\n{parTwo}"
docEditor.replaceText(text) docEditor.replaceText(text)
assert docEditor.getText() == text assert docEditor.getText() == text
@@ -1290,7 +1290,7 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, projPath, ipsumText
# Invalid and Generic # Invalid and Generic
# =================== # ===================
text = "### A Scene\n\n%s" % ipsumText[0] text = f"### A Scene\n\n{ipsumText[0]}"
docEditor.replaceText(text) docEditor.replaceText(text)
# Invalid Block # Invalid Block
+10 -10
View File
@@ -276,11 +276,11 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
# Other Checks # Other Checks
# Replace Quotes # Replace Quotes
docEditor.setPlainText(( docEditor.setPlainText(
"### New Text\n\n" "### New Text\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n" "Text with 'single' quotes and 'tricky stuff's'.\n\n"
"Also text with \"double\" quotes which are \"less tricky\".\n\n" "Also text with \"double\" quotes which are \"less tricky\".\n\n"
)) )
mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger) mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger)
mainMenu.aFmtReplSng.activate(QAction.ActionEvent.Trigger) mainMenu.aFmtReplSng.activate(QAction.ActionEvent.Trigger)
@@ -299,14 +299,14 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
) )
# Remove in-paragraph line breaks # Remove in-paragraph line breaks
docEditor.setPlainText(( docEditor.setPlainText(
"### New Text\n\n" "### New Text\n\n"
"@char: Someone\n" "@char: Someone\n"
"@location: Somewhere\n\n" "@location: Somewhere\n\n"
"% Some comment ...\n\n" "% Some comment ...\n\n"
"Here is some text\non multiple\nlines.\n\n" "Here is some text\non multiple\nlines.\n\n"
"With another paragraph\nhere." "With another paragraph\nhere."
)) )
mainMenu.aFmtRmBreaks.activate(QAction.ActionEvent.Trigger) mainMenu.aFmtRmBreaks.activate(QAction.ActionEvent.Trigger)
assert docEditor.getText() == ( assert docEditor.getText() == (
"### New Text\n\n" "### New Text\n\n"
@@ -317,14 +317,14 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"With another paragraph here.\n" "With another paragraph here.\n"
) )
docEditor.setPlainText(( docEditor.setPlainText(
"### New Text\n\n" "### New Text\n\n"
"@char: Someone\n" "@char: Someone\n"
"@location: Somewhere\n\n" "@location: Somewhere\n\n"
"% Some comment ...\n\n" "% Some comment ...\n\n"
"Here is some text\non multiple\nlines.\n\n" "Here is some text\non multiple\nlines.\n\n"
"With another paragraph\nhere." "With another paragraph\nhere."
)) )
cursor = docEditor.textCursor() cursor = docEditor.textCursor()
cursor.setPosition(74) cursor.setPosition(74)
cursor.movePosition(QtMoveRight, QtKeepAnchor, 29) cursor.movePosition(QtMoveRight, QtKeepAnchor, 29)
@@ -343,12 +343,12 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
assert not docEditor.docAction(nwDocAction.NO_ACTION) assert not docEditor.docAction(nwDocAction.NO_ACTION)
# Test Invalid Formats # Test Invalid Formats
docEditor.setPlainText(( docEditor.setPlainText(
"### New Text\n\n" "### New Text\n\n"
"@tag: Bod\n\n" "@tag: Bod\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n" "Text with 'single' quotes and 'tricky stuff's'.\n\n"
"Also text with \"double\" quotes which are \"less tricky\".\n\n" "Also text with \"double\" quotes which are \"less tricky\".\n\n"
)) )
# Cannot Format Tag # Cannot Format Tag
docEditor.setCursorPosition(17) docEditor.setCursorPosition(17)
@@ -489,7 +489,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd
# Insert Keywords # Insert Keywords
# =============== # ===============
for action, key in zip(mainMenu.mInsKeywords.actions(), nwKeyWords.ALL_KEYS): for action, key in zip(mainMenu.mInsKeywords.actions(), nwKeyWords.ALL_KEYS, strict=False):
docEditor.setPlainText("Stuff") docEditor.setPlainText("Stuff")
action.activate(QAction.ActionEvent.Trigger) action.activate(QAction.ActionEvent.Trigger)
assert docEditor.getText() == f"Stuff\n{key}: " assert docEditor.getText() == f"Stuff\n{key}: "
@@ -503,7 +503,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd
# Insert Fields # Insert Fields
# ============= # =============
for action, field in zip(mainMenu.mInsField.actions(), nwStats.ALL_FIELDS): for action, field in zip(mainMenu.mInsField.actions(), nwStats.ALL_FIELDS, strict=False):
value = nwShortcode.FIELD_VALUE.format(field) value = nwShortcode.FIELD_VALUE.format(field)
docEditor.setPlainText("Stuff ") docEditor.setPlainText("Stuff ")
docEditor.setCursorPosition(6) docEditor.setCursorPosition(6)
+16 -16
View File
@@ -86,7 +86,7 @@ def testTextCounting_standardCounter():
assert standardCounter(" <") == (0, 0, 0) assert standardCounter(" <") == (0, 0, 0)
# General Text # General Text
cC, wC, pC = standardCounter(( cC, wC, pC = standardCounter(
"#! Title\n\n" "#! Title\n\n"
"##! Prologue\n\n" "##! Prologue\n\n"
"# Heading One\n" "# Heading One\n"
@@ -100,47 +100,47 @@ def testTextCounting_standardCounter():
"The second paragraph.\n\n\n" "The second paragraph.\n\n\n"
"The third paragraph.\n\n" "The third paragraph.\n\n"
"Dashes\u2013and even longer\u2014dashes." "Dashes\u2013and even longer\u2014dashes."
)) )
assert cC == 163 assert cC == 163
assert wC == 26 assert wC == 26
assert pC == 4 assert pC == 4
# Text Alignment # Text Alignment
cC, wC, pC = standardCounter(( cC, wC, pC = standardCounter(
"# Title\n\n" "# Title\n\n"
"Left aligned<<\n\n" "Left aligned<<\n\n"
"Left aligned <<\n\n" "Left aligned <<\n\n"
"Right indent<\n\n" "Right indent<\n\n"
"Right indent <\n\n" "Right indent <\n\n"
)) )
assert cC == 53 assert cC == 53
assert wC == 9 assert wC == 9
assert pC == 4 assert pC == 4
cC, wC, pC = standardCounter(( cC, wC, pC = standardCounter(
"# Title\n\n" "# Title\n\n"
">>Right aligned\n\n" ">>Right aligned\n\n"
">> Right aligned\n\n" ">> Right aligned\n\n"
">Left indent\n\n" ">Left indent\n\n"
"> Left indent\n\n" "> Left indent\n\n"
)) )
assert cC == 53 assert cC == 53
assert wC == 9 assert wC == 9
assert pC == 4 assert pC == 4
cC, wC, pC = standardCounter(( cC, wC, pC = standardCounter(
"# Title\n\n" "# Title\n\n"
">>Centre aligned<<\n\n" ">>Centre aligned<<\n\n"
">> Centre aligned <<\n\n" ">> Centre aligned <<\n\n"
">Double indent<\n\n" ">Double indent<\n\n"
"> Double indent <\n\n" "> Double indent <\n\n"
)) )
assert cC == 59 assert cC == 59
assert wC == 9 assert wC == 9
assert pC == 4 assert pC == 4
# Formatting Codes, Upper Case (Old Implementation) # Formatting Codes, Upper Case (Old Implementation)
cC, wC, pC = standardCounter(( cC, wC, pC = standardCounter(
"Some text\n\n" "Some text\n\n"
"[NEWPAGE]\n\n" "[NEWPAGE]\n\n"
"more text\n\n" "more text\n\n"
@@ -150,13 +150,13 @@ def testTextCounting_standardCounter():
"and some final text\n\n" "and some final text\n\n"
"[VSPACE:4]\n\n" "[VSPACE:4]\n\n"
"THE END\n\n" "THE END\n\n"
)) )
assert cC == 58 assert cC == 58
assert wC == 13 assert wC == 13
assert pC == 5 assert pC == 5
# Formatting Codes, Lower Case (Current Implementation) # Formatting Codes, Lower Case (Current Implementation)
cC, wC, pC = standardCounter(( cC, wC, pC = standardCounter(
"Some text\n\n" "Some text\n\n"
"[newpage]\n\n" "[newpage]\n\n"
"more text\n\n" "more text\n\n"
@@ -166,16 +166,16 @@ def testTextCounting_standardCounter():
"and some final text\n\n" "and some final text\n\n"
"[vspace:4]\n\n" "[vspace:4]\n\n"
"THE END\n\n" "THE END\n\n"
)) )
assert cC == 58 assert cC == 58
assert wC == 13 assert wC == 13
assert pC == 5 assert pC == 5
# Check ShortCodes # Check ShortCodes
cC, wC, pC = standardCounter(( cC, wC, pC = standardCounter(
"Text with [b]bold[/b] text and padded [b] bold [/b] text.\n\n" "Text with [b]bold[/b] text and padded [b] bold [/b] text.\n\n"
"Text with [b][i] nested [/i] emphasis [/b] in it.\n\n" "Text with [b][i] nested [/i] emphasis [/b] in it.\n\n"
)) )
assert cC == 78 assert cC == 78
assert wC == 14 assert wC == 14
assert pC == 2 assert pC == 2
@@ -188,7 +188,7 @@ def testTextCounting_bodyTextCounter():
assert bodyTextCounter(None) == (0, 0, 0) # type: ignore assert bodyTextCounter(None) == (0, 0, 0) # type: ignore
# General Text # General Text
wC, cC, sC = bodyTextCounter(( wC, cC, sC = bodyTextCounter(
"#! Title\n\n" "#! Title\n\n"
"##! Prologue\n\n" "##! Prologue\n\n"
"# Heading One\n" "# Heading One\n"
@@ -201,7 +201,7 @@ def testTextCounting_bodyTextCounter():
"The second paragraph.\n\n\n" "The second paragraph.\n\n\n"
"The third paragraph.\n\n" "The third paragraph.\n\n"
"Dashes\u2013and even longer\u2014dashes." "Dashes\u2013and even longer\u2014dashes."
)) )
assert wC == 14 assert wC == 14
assert cC == 91 assert cC == 91
assert sC == 81 assert sC == 81
+2 -2
View File
@@ -293,7 +293,7 @@ def testToolManuscript_Features(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
obj.close() obj.close()
break break
else: else:
assert False raise AssertionError
# Finish # Finish
manus.close() manus.close()
@@ -327,7 +327,7 @@ def testToolManuscript_Print(monkeypatch, qtbot, nwGUI, projPath):
obj.close() obj.close()
break break
else: else:
assert False raise AssertionError
# Finish # Finish
manus.close() manus.close()
+57 -57
View File
@@ -121,41 +121,41 @@ def testToolWritingStats_Export(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
# Sort by time # Sort by time
sessLog.listBox.sortByColumn(sessLog.C_TIME, Qt.SortOrder.AscendingOrder) sessLog.listBox.sortByColumn(sessLog.C_TIME, Qt.SortOrder.AscendingOrder)
assert sessLog.novelWords.text() == "{:n}".format(600) assert sessLog.novelWords.text() == f"{600:n}"
assert sessLog.notesWords.text() == "{:n}".format(275) assert sessLog.notesWords.text() == f"{275:n}"
assert sessLog.totalWords.text() == "{:n}".format(875) assert sessLog.totalWords.text() == f"{875:n}"
item = sessLog.listBox.topLevelItem(0) item = sessLog.listBox.topLevelItem(0)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(1) assert item.text(sessLog.C_COUNT) == f"{1:n}"
item = sessLog.listBox.topLevelItem(1) item = sessLog.listBox.topLevelItem(1)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) assert item.text(sessLog.C_COUNT) == f"{-200:n}"
item = sessLog.listBox.topLevelItem(2) item = sessLog.listBox.topLevelItem(2)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(300) assert item.text(sessLog.C_COUNT) == f"{300:n}"
item = sessLog.listBox.topLevelItem(3) item = sessLog.listBox.topLevelItem(3)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) assert item.text(sessLog.C_COUNT) == f"{-120:n}"
item = sessLog.listBox.topLevelItem(4) item = sessLog.listBox.topLevelItem(4)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) assert item.text(sessLog.C_COUNT) == f"{-20:n}"
item = sessLog.listBox.topLevelItem(5) item = sessLog.listBox.topLevelItem(5)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(40) assert item.text(sessLog.C_COUNT) == f"{40:n}"
item = sessLog.listBox.topLevelItem(6) item = sessLog.listBox.topLevelItem(6)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) assert item.text(sessLog.C_COUNT) == f"{-400:n}"
item = sessLog.listBox.topLevelItem(7) item = sessLog.listBox.topLevelItem(7)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(200) assert item.text(sessLog.C_COUNT) == f"{200:n}"
assert sessLog._saveData(sessLog.FMT_CSV) assert sessLog._saveData(sessLog.FMT_CSV)
assert sessLog._saveData(sessLog.FMT_JSON) assert sessLog._saveData(sessLog.FMT_JSON)
@@ -230,35 +230,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0) item = sessLog.listBox.topLevelItem(0)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(1) assert item.text(sessLog.C_COUNT) == f"{1:n}"
item = sessLog.listBox.topLevelItem(1) item = sessLog.listBox.topLevelItem(1)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) assert item.text(sessLog.C_COUNT) == f"{-200:n}"
item = sessLog.listBox.topLevelItem(2) item = sessLog.listBox.topLevelItem(2)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(300) assert item.text(sessLog.C_COUNT) == f"{300:n}"
item = sessLog.listBox.topLevelItem(3) item = sessLog.listBox.topLevelItem(3)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) assert item.text(sessLog.C_COUNT) == f"{-120:n}"
item = sessLog.listBox.topLevelItem(4) item = sessLog.listBox.topLevelItem(4)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) assert item.text(sessLog.C_COUNT) == f"{-20:n}"
item = sessLog.listBox.topLevelItem(5) item = sessLog.listBox.topLevelItem(5)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(40) assert item.text(sessLog.C_COUNT) == f"{40:n}"
item = sessLog.listBox.topLevelItem(6) item = sessLog.listBox.topLevelItem(6)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) assert item.text(sessLog.C_COUNT) == f"{-400:n}"
item = sessLog.listBox.topLevelItem(7) item = sessLog.listBox.topLevelItem(7)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(200) assert item.text(sessLog.C_COUNT) == f"{200:n}"
# No Novel Files # No Novel Files
qtbot.mouseClick(sessLog.incNovel, QtMouseLeft) qtbot.mouseClick(sessLog.incNovel, QtMouseLeft)
@@ -270,35 +270,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0) item = sessLog.listBox.topLevelItem(0)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(1) assert item.text(sessLog.C_COUNT) == f"{1:n}"
item = sessLog.listBox.topLevelItem(1) item = sessLog.listBox.topLevelItem(1)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-100) assert item.text(sessLog.C_COUNT) == f"{-100:n}"
item = sessLog.listBox.topLevelItem(2) item = sessLog.listBox.topLevelItem(2)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(150) assert item.text(sessLog.C_COUNT) == f"{150:n}"
item = sessLog.listBox.topLevelItem(3) item = sessLog.listBox.topLevelItem(3)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-60) assert item.text(sessLog.C_COUNT) == f"{-60:n}"
item = sessLog.listBox.topLevelItem(4) item = sessLog.listBox.topLevelItem(4)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-10) assert item.text(sessLog.C_COUNT) == f"{-10:n}"
item = sessLog.listBox.topLevelItem(5) item = sessLog.listBox.topLevelItem(5)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(20) assert item.text(sessLog.C_COUNT) == f"{20:n}"
item = sessLog.listBox.topLevelItem(6) item = sessLog.listBox.topLevelItem(6)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) assert item.text(sessLog.C_COUNT) == f"{-200:n}"
item = sessLog.listBox.topLevelItem(7) item = sessLog.listBox.topLevelItem(7)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(100) assert item.text(sessLog.C_COUNT) == f"{100:n}"
assert jsonData == [ assert jsonData == [
{ {
@@ -339,35 +339,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0) item = sessLog.listBox.topLevelItem(0)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(1) assert item.text(sessLog.C_COUNT) == f"{1:n}"
item = sessLog.listBox.topLevelItem(1) item = sessLog.listBox.topLevelItem(1)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-100) assert item.text(sessLog.C_COUNT) == f"{-100:n}"
item = sessLog.listBox.topLevelItem(2) item = sessLog.listBox.topLevelItem(2)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(150) assert item.text(sessLog.C_COUNT) == f"{150:n}"
item = sessLog.listBox.topLevelItem(3) item = sessLog.listBox.topLevelItem(3)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-60) assert item.text(sessLog.C_COUNT) == f"{-60:n}"
item = sessLog.listBox.topLevelItem(4) item = sessLog.listBox.topLevelItem(4)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-10) assert item.text(sessLog.C_COUNT) == f"{-10:n}"
item = sessLog.listBox.topLevelItem(5) item = sessLog.listBox.topLevelItem(5)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(20) assert item.text(sessLog.C_COUNT) == f"{20:n}"
item = sessLog.listBox.topLevelItem(6) item = sessLog.listBox.topLevelItem(6)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) assert item.text(sessLog.C_COUNT) == f"{-200:n}"
item = sessLog.listBox.topLevelItem(7) item = sessLog.listBox.topLevelItem(7)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(100) assert item.text(sessLog.C_COUNT) == f"{100:n}"
assert jsonData == [ assert jsonData == [
{ {
@@ -408,19 +408,19 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0) item = sessLog.listBox.topLevelItem(0)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(1) assert item.text(sessLog.C_COUNT) == f"{1:n}"
item = sessLog.listBox.topLevelItem(1) item = sessLog.listBox.topLevelItem(1)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(300) assert item.text(sessLog.C_COUNT) == f"{300:n}"
item = sessLog.listBox.topLevelItem(2) item = sessLog.listBox.topLevelItem(2)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(40) assert item.text(sessLog.C_COUNT) == f"{40:n}"
item = sessLog.listBox.topLevelItem(3) item = sessLog.listBox.topLevelItem(3)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(200) assert item.text(sessLog.C_COUNT) == f"{200:n}"
assert jsonData == [ assert jsonData == [
{ {
@@ -449,43 +449,43 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0) item = sessLog.listBox.topLevelItem(0)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(1) assert item.text(sessLog.C_COUNT) == f"{1:n}"
item = sessLog.listBox.topLevelItem(1) item = sessLog.listBox.topLevelItem(1)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(0) assert item.text(sessLog.C_COUNT) == f"{0:n}"
item = sessLog.listBox.topLevelItem(2) item = sessLog.listBox.topLevelItem(2)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) assert item.text(sessLog.C_COUNT) == f"{-200:n}"
item = sessLog.listBox.topLevelItem(3) item = sessLog.listBox.topLevelItem(3)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(300) assert item.text(sessLog.C_COUNT) == f"{300:n}"
item = sessLog.listBox.topLevelItem(4) item = sessLog.listBox.topLevelItem(4)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) assert item.text(sessLog.C_COUNT) == f"{-120:n}"
item = sessLog.listBox.topLevelItem(5) item = sessLog.listBox.topLevelItem(5)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) assert item.text(sessLog.C_COUNT) == f"{-20:n}"
item = sessLog.listBox.topLevelItem(6) item = sessLog.listBox.topLevelItem(6)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(40) assert item.text(sessLog.C_COUNT) == f"{40:n}"
item = sessLog.listBox.topLevelItem(7) item = sessLog.listBox.topLevelItem(7)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) assert item.text(sessLog.C_COUNT) == f"{-400:n}"
item = sessLog.listBox.topLevelItem(8) item = sessLog.listBox.topLevelItem(8)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(200) assert item.text(sessLog.C_COUNT) == f"{200:n}"
item = sessLog.listBox.topLevelItem(9) item = sessLog.listBox.topLevelItem(9)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(0) assert item.text(sessLog.C_COUNT) == f"{0:n}"
assert jsonData == [ assert jsonData == [
{ {
@@ -542,35 +542,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0) item = sessLog.listBox.topLevelItem(0)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(1) assert item.text(sessLog.C_COUNT) == f"{1:n}"
item = sessLog.listBox.topLevelItem(1) item = sessLog.listBox.topLevelItem(1)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) assert item.text(sessLog.C_COUNT) == f"{-200:n}"
item = sessLog.listBox.topLevelItem(2) item = sessLog.listBox.topLevelItem(2)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(180) assert item.text(sessLog.C_COUNT) == f"{180:n}"
item = sessLog.listBox.topLevelItem(3) item = sessLog.listBox.topLevelItem(3)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) assert item.text(sessLog.C_COUNT) == f"{-20:n}"
item = sessLog.listBox.topLevelItem(4) item = sessLog.listBox.topLevelItem(4)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(40) assert item.text(sessLog.C_COUNT) == f"{40:n}"
item = sessLog.listBox.topLevelItem(5) item = sessLog.listBox.topLevelItem(5)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) assert item.text(sessLog.C_COUNT) == f"{-400:n}"
item = sessLog.listBox.topLevelItem(6) item = sessLog.listBox.topLevelItem(6)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(200) assert item.text(sessLog.C_COUNT) == f"{200:n}"
item = sessLog.listBox.topLevelItem(7) item = sessLog.listBox.topLevelItem(7)
assert item is not None assert item is not None
assert item.text(sessLog.C_COUNT) == "{:n}".format(0) assert item.text(sessLog.C_COUNT) == f"{0:n}"
assert jsonData == [ assert jsonData == [
{ {
+7 -7
View File
@@ -97,18 +97,18 @@ def cmpFiles(
lnTwo = txtTwo[n].strip() lnTwo = txtTwo[n].strip()
if n+1 in ignoreLines: if n+1 in ignoreLines:
print("Ignoring line %d" % (n+1)) print(f"Ignoring line {n+1}")
continue continue
if ignoreStart is not None: if ignoreStart is not None:
if lnOne.startswith(ignoreStart): if lnOne.startswith(ignoreStart):
print("Ignoring line %d" % (n+1)) print(f"Ignoring line {n+1}")
continue continue
if lnOne != lnTwo: if lnOne != lnTwo:
print("Diff on line %d:" % (n+1)) print(f"Diff on line {n+1}:")
print(" << '%s'" % lnOne) print(f" << '{lnOne}'")
print(" >> '%s'" % lnTwo) print(f" >> '{lnTwo}'")
diffFound = True diffFound = True
return not diffFound return not diffFound
@@ -205,11 +205,11 @@ def buildTestProject(obj: object, projPath: Path) -> None:
project.index.reIndexHandle(tdHandle) project.index.reIndexHandle(tdHandle)
aDoc = project.storage.getDocument(cdHandle) aDoc = project.storage.getDocument(cdHandle)
aDoc.writeDocument("## %s\n\n" % project.tr("New Chapter")) aDoc.writeDocument("## {0}\n\n".format(project.tr("New Chapter")))
project.index.reIndexHandle(cdHandle) project.index.reIndexHandle(cdHandle)
aDoc = project.storage.getDocument(sdHandle) aDoc = project.storage.getDocument(sdHandle)
aDoc.writeDocument("### %s\n\n" % project.tr("New Scene")) aDoc.writeDocument("### {0}\n\n".format(project.tr("New Scene")))
project.index.reIndexHandle(sdHandle) project.index.reIndexHandle(sdHandle)
project.session.startSession() project.session.startSession()
+2 -2
View File
@@ -102,7 +102,7 @@ def buildSampleZip(args: argparse.Namespace | None = None) -> None:
sys.exit(1) sys.exit(1)
print("") print("")
print("Built file: %s" % dstSample) print(f"Built file: {dstSample}")
print("") print("")
return return
@@ -242,7 +242,7 @@ def buildTranslationAssets(args: argparse.Namespace | None = None) -> None:
for item in srcDir.iterdir(): for item in srcDir.iterdir():
if item.is_file() and item.suffix == ".qm": if item.is_file() and item.suffix == ".qm":
item.rename(dstDir / item.name) item.rename(dstDir / item.name)
print("Moved: %s -> %s" % (item.relative_to(ROOT_DIR), dstRel / item.name)) print(f"Moved: {item.relative_to(ROOT_DIR)} -> {dstRel / item.name}")
print("") print("")
+3 -3
View File
@@ -65,12 +65,12 @@ def embedPython(bldDir: Path, outDir: Path) -> None:
"""Embed Python library.""" """Embed Python library."""
print("Adding Python embeddable ...") print("Adding Python embeddable ...")
pyVers = "%d.%d.%d" % (sys.version_info[:3]) pyVers = ".".join(str(v) for v in sys.version_info[:3])
zipFile = f"python-{pyVers}-embed-amd64.zip" zipFile = f"python-{pyVers}-embed-amd64.zip"
pyZip = bldDir / zipFile pyZip = bldDir / zipFile
if not pyZip.is_file(): if not pyZip.is_file():
pyUrl = f"https://www.python.org/ftp/python/{pyVers}/{zipFile}" pyUrl = f"https://www.python.org/ftp/python/{pyVers}/{zipFile}"
print("Downloading: %s" % pyUrl) print(f"Downloading: {pyUrl}")
urllib.request.urlretrieve(pyUrl, pyZip) urllib.request.urlretrieve(pyUrl, pyZip)
print("Extracting ...") print("Extracting ...")
@@ -192,7 +192,7 @@ def main(args: argparse.Namespace) -> None:
print("") print("")
numVers, _, _ = extractVersion() numVers, _, _ = extractVersion()
print("Version: %s" % numVers) print(f"Version: {numVers}")
bldDir = ROOT_DIR / "dist" bldDir = ROOT_DIR / "dist"
outDir = bldDir / "novelWriter" outDir = bldDir / "novelWriter"
+6 -6
View File
@@ -44,17 +44,17 @@ def extractVersion(beQuiet: bool = False) -> tuple[str, str, str]:
try: try:
for aLine in initFile.read_text(encoding="utf-8").splitlines(): for aLine in initFile.read_text(encoding="utf-8").splitlines():
if aLine.startswith("__version__"): if aLine.startswith("__version__"):
numVers = getValue((aLine)) numVers = getValue(aLine)
if aLine.startswith("__hexversion__"): if aLine.startswith("__hexversion__"):
hexVers = getValue((aLine)) hexVers = getValue(aLine)
if aLine.startswith("__date__"): if aLine.startswith("__date__"):
relDate = getValue((aLine)) relDate = getValue(aLine)
except Exception as exc: except Exception as exc:
print("Could not read file: %s" % initFile) print(f"Could not read file: {initFile}")
print(str(exc)) print(str(exc))
if not beQuiet: if not beQuiet:
print("novelWriter version: %s (%s) at %s" % (numVers, hexVers, relDate)) print(f"novelWriter version: {numVers} ({hexVers}) at {relDate}")
return numVers, hexVers, relDate return numVers, hexVers, relDate
@@ -95,7 +95,7 @@ def copyPackageFiles(dst: Path, setupPy: bool = False) -> None:
copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"] copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"]
for copyFile in copyFiles: for copyFile in copyFiles:
shutil.copyfile(copyFile, dst / copyFile) shutil.copyfile(copyFile, dst / copyFile)
print("Copied: %s" % copyFile) print(f"Copied: {copyFile}")
writeFile(dst / "MANIFEST.in", ( writeFile(dst / "MANIFEST.in", (
"include LICENSE.md\n" "include LICENSE.md\n"
+2 -2
View File
@@ -165,7 +165,7 @@ def _fixXml(svg: ET.Element) -> str:
def _writeThemeFile( def _writeThemeFile(
path: Path, name: str, author: str, license: str, icons: dict[str, ET.Element] path: Path, name: str, author: str, license_: str, icons: dict[str, ET.Element]
) -> None: ) -> None:
"""Write an icon theme file.""" """Write an icon theme file."""
with open(path.with_suffix(".icons"), mode="w", encoding="utf-8") as out: with open(path.with_suffix(".icons"), mode="w", encoding="utf-8") as out:
@@ -173,7 +173,7 @@ def _writeThemeFile(
out.write("# Meta\n") out.write("# Meta\n")
out.write(f"meta:name = {name}\n") out.write(f"meta:name = {name}\n")
out.write(f"meta:author = {author}\n") out.write(f"meta:author = {author}\n")
out.write(f"meta:license = {license}\n") out.write(f"meta:license = {license_}\n")
out.write("\n") out.write("\n")
out.write("# Icons\n") out.write("# Icons\n")
for key, svg in icons.items(): for key, svg in icons.items():