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
- name: Install Dependencies
run: pip install -r requirements.txt -r requirements-dev.txt
- name: Run Flake8
- name: Run Ruff
run: |
flake8 --version
flake8 novelwriter --count --show-source --statistics
flake8 tests --count --show-source --statistics --extend-ignore ANN
ruff --version
ruff check
- name: Run Pyright
run: |
pyright --version
+1 -1
View File
@@ -12,7 +12,7 @@ import time
# -- Project Information -----------------------------------------------------
project = "novelWriter"
copyright = f"{datetime.date.today().year}"
copyright = f"{datetime.date.today().year}" # noqa: A001
tmp_authors = ["Veronica Berglyd Olsen"]
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)
sys.exit(0)
elif inOpt in ("-v", "--version"):
print("novelWriter Version %s [%s]" % (__version__, __date__))
print(f"novelWriter Version {__version__} [{__date__}]")
sys.exit(0)
elif inOpt in ("-i", "--info"):
logLevel = logging.INFO
@@ -207,17 +207,17 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
errorCode = 0
if sys.hexversion < 0x030a00f0:
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
if CONFIG.verQtValue < 0x060400:
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
if CONFIG.verPyQtValue < 0x060400:
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
@@ -228,9 +228,9 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
errDlg.showMessage((
"<h3>A critical error was encountered</h3>"
"<p>novelWriter cannot start due to the following issues:<p>"
"<p>&nbsp;-&nbsp;%s</p>"
"<p>&nbsp;-&nbsp;{0}</p>"
"<p>Shutting down ...</p>"
) % (
).format(
"<br>&nbsp;-&nbsp;".join(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
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 "[]"
buffer = []
+4 -4
View File
@@ -355,7 +355,7 @@ class Config:
"""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.
"""
if isinstance(path, (str, Path)):
if isinstance(path, str | Path):
path = checkPath(path, self._homePath)
if not path.is_dir():
path = path.parent
@@ -504,10 +504,10 @@ class Config:
and dataPath is mainly intended for the test suite.
"""
logger.debug("Initialising Config ...")
if isinstance(confPath, (str, Path)):
if isinstance(confPath, str | Path):
logger.info("Setting alternative config path: %s", confPath)
self._confPath = Path(confPath)
if isinstance(dataPath, (str, Path)):
if isinstance(dataPath, str | Path):
logger.info("Setting alternative data path: %s", dataPath)
self._dataPath = Path(dataPath)
@@ -555,7 +555,7 @@ class Config:
for lngPath, lngBase in langList:
for lngCode in self._qLocale.uiLanguages():
qTrans = QTranslator()
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
lngFile = "{0}_{1}".format(lngBase, lngCode.replace("-", "_"))
if lngFile not in self._qtTrans:
if qTrans.load(lngFile, lngPath):
logger.debug("Loaded: %s.qm", lngFile)
+1 -1
View File
@@ -643,7 +643,7 @@ class nwUnicode:
H_LTRIS = "&#9666;"
class nwHtmlUnicode():
class nwHtmlUnicode:
U_TO_H = {
# Quotes
+2 -2
View File
@@ -294,12 +294,12 @@ class BuildSettings:
def getInt(self, key: str) -> int:
"""Type safe value access for integers."""
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:
"""Type safe value access for floats."""
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
+2 -2
View File
@@ -379,7 +379,7 @@ class ProjectBuilder:
"""Build or copy a project from a data dictionary."""
if isinstance(data, dict):
path = data.get("path", None) or None
if isinstance(path, (str, Path)):
if isinstance(path, str | Path):
self._path = Path(path).resolve()
if data.get("sample", False):
return self._extractSampleProject(self._path)
@@ -509,7 +509,7 @@ class ProjectBuilder:
# Also add the archive and trash folders
project.newRoot(nwItemClass.ARCHIVE)
project.tree.trash # Triggers the creation of Trash
_ = project.tree.trash # Triggers the creation of Trash
project.saveProject()
project.closeProject()
+2 -2
View File
@@ -316,13 +316,13 @@ class NWBuildDocument:
bldObj.setStyles(self._build.getBool("html.addStyles"))
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
if isinstance(bldObj, (ToOdt, ToDocX)):
if isinstance(bldObj, ToOdt | ToDocX):
bldObj.setHeaderFormat(
self._build.getStr("doc.pageHeader"),
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)
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
bldObj.setPageLayout(
+2 -1
View File
@@ -333,8 +333,9 @@ class ProjectModel(QAbstractItemModel):
return self.createIndex(parent.row(), 0, parent)
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."""
parent = parent or QModelIndex()
if self.hasIndex(row, column, parent):
node: ProjectNode = parent.internalPointer() if parent.isValid() else self._root
if child := node.child(row):
+1 -1
View File
@@ -691,7 +691,7 @@ class _ReplacePage(NFixedPage):
self.listBox.itemSelectionChanged.connect(self._onSelectionChanged)
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.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder)
+1 -1
View File
@@ -78,7 +78,7 @@ class GuiQuoteSelect(NDialog):
minSize = 100
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())
qtItem = QListWidgetItem(text)
qtItem.setData(self.D_KEY, sKey)
+2 -2
View File
@@ -144,7 +144,7 @@ class NWErrorMessage(QDialog):
try:
txtTrace = "\n".join(format_tb(exTrace))
self.msgBody.setPlainText((
self.msgBody.setPlainText(
"Environment:\n"
f"novelWriter Version: {__version__}\n"
f"Host OS: {sys.platform} ({kernelVersion})\n"
@@ -153,7 +153,7 @@ class NWErrorMessage(QDialog):
f"enchant: {enchantVersion}\n\n"
f"{exType.__name__}:\n{str(exValue)}\n\n"
f"Traceback:\n{txtTrace}\n"
))
)
except Exception:
self.msgBody.setPlainText("Failed to generate error report ...")
+1 -1
View File
@@ -48,7 +48,7 @@ class WheelEventFilter(QObject):
self._locked = False
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
parent widget's wheelEvent handler.
"""
+4 -4
View File
@@ -155,15 +155,15 @@ class NDoubleSpinBox(QDoubleSpinBox):
self,
parent: QWidget | None = None,
*,
min: float = 0.0,
max: float = 15.0,
minVal: float = 0.0,
maxVal: float = 15.0,
step: float = 0.1,
prec: int = 2,
) -> None:
super().__init__(parent=parent)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setMinimum(min)
self.setMaximum(max)
self.setMinimum(minVal)
self.setMaximum(maxVal)
self.setSingleStep(step)
self.setDecimals(prec)
return
+2 -2
View File
@@ -212,9 +212,9 @@ class ToDocX(Tokenizer):
self._pageMargins = QMargins(_mmToSz(left), _mmToSz(top), _mmToSz(right), _mmToSz(bottom))
return
def setHeaderFormat(self, format: str, offset: int) -> None:
def setHeaderFormat(self, value: str, offset: int) -> None:
"""Set the document header format."""
self._headerFormat = format.strip()
self._headerFormat = value.strip()
self._pageOffset = offset
return
+3 -3
View File
@@ -203,9 +203,9 @@ class ToOdt(Tokenizer):
self._mDocRight = f"{right/10.0:.3f}cm"
return
def setHeaderFormat(self, format: str, offset: int) -> None:
def setHeaderFormat(self, value: str, offset: int) -> None:
"""Set the document header format."""
self._headerFormat = format.strip()
self._headerFormat = value.strip()
self._pageOffset = offset
return
@@ -1509,7 +1509,7 @@ class XMLParagraph:
errMsg = ""
nMissed = len(self._rawTxt) - self._chrPos
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
##
+7 -5
View File
@@ -978,7 +978,7 @@ class GuiDocEditor(QPlainTextEdit):
super().dropEvent(event)
return
def focusNextPrevChild(self, next: bool) -> bool:
def focusNextPrevChild(self, _next: bool) -> bool:
"""Capture the focus request from the tab key on the text
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,
@@ -1037,7 +1037,7 @@ class GuiDocEditor(QPlainTextEdit):
logger.error("Invalid keyword '%s'", keyword)
return False
logger.debug("Inserting keyword '%s'", keyword)
state = self.insertNewBlock("%s: " % keyword)
state = self.insertNewBlock(f"{keyword}: ")
return state
@pyqtSlot()
@@ -1528,10 +1528,10 @@ class GuiDocEditor(QPlainTextEdit):
if fLen == min(numA, numB):
cursor.beginEditBlock()
cursor.setPosition(posS)
for i in range(fLen):
for _ in range(fLen):
cursor.deletePreviousChar()
cursor.setPosition(posE)
for i in range(fLen):
for _ in range(fLen):
cursor.deletePreviousChar()
cursor.endEditBlock()
@@ -1943,7 +1943,9 @@ class GuiDocEditor(QPlainTextEdit):
exist = False
cPos = cursor.selectionStart() - block.position()
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:
# The cursor is between the start of two tags
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.
"""
if CONFIG.isDebug: # pragma: no cover
for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory)):
logger.debug(
"History %02d: %s %13s [x:%d]" % (
i + 1, ">" if i == self._currPos else " ", h, p
)
)
for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory, strict=False)):
a = ">" if i == self._currPos else " "
logger.debug(f"History {i + 1:02d}: {a} {h:13s} [x:{p}]")
return
+3 -3
View File
@@ -936,7 +936,7 @@ class GuiMainMenu(QMenuBar):
# Search > Find in Project
self.aFindProj = qtAddAction(self.srcMenu, self.tr("Find in Project"))
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
@@ -956,10 +956,10 @@ class GuiMainMenu(QMenuBar):
self.mSelectLanguage = qtAddMenu(self.toolsMenu, self.tr("Spell Check Language"))
languages = SHARED.spelling.listDictionaries()
languages.insert(0, ("None", self.tr("Default")))
for n, (tag, language) in enumerate(languages):
for tag, language in languages:
aSpell = QAction(self.mSelectLanguage)
aSpell.setText(language)
aSpell.triggered.connect(lambda n, tag=tag: self._changeSpelling(tag))
aSpell.triggered.connect(qtLambda(self._changeSpelling, tag))
self.mSelectLanguage.addAction(aSpell)
# Tools > Re-Run Spell Check
+4 -4
View File
@@ -259,17 +259,17 @@ class GuiProjectToolBar(QWidget):
self.mQuick = QMenu(self)
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.setMenu(self.mQuick)
# Move Buttons
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.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)
# Add Item Menu
@@ -309,7 +309,7 @@ class GuiProjectToolBar(QWidget):
self._buildRootMenu()
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.setMenu(self.mAdd)
+1 -1
View File
@@ -155,4 +155,4 @@ class _PopRightMenu(QMenu):
if isinstance(parent := self.parent(), QWidget):
offset = QPoint(parent.width(), parent.height() - self.height())
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
if not isYes:
msgYes = SHARED.question("%s<br>%s" % (
msgYes = SHARED.question("{0}<br>{1}".format(
self.tr("Close the current project?"),
self.tr("Changes are saved automatically.")
))
@@ -844,7 +844,7 @@ class GuiMain(QMainWindow):
def closeMain(self) -> bool:
"""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("Changes are saved automatically.")
)):
+4 -4
View File
@@ -101,28 +101,28 @@ class SharedData(QObject):
def mainGui(self) -> GuiMain:
"""Return the Main GUI instance."""
if self._gui is None:
raise Exception("SharedData class not fully initialised")
raise RuntimeError("SharedData class not fully initialised")
return self._gui
@property
def theme(self) -> GuiTheme:
"""Return the GUI Theme instance."""
if self._theme is None:
raise Exception("SharedData class not fully initialised")
raise RuntimeError("SharedData class not fully initialised")
return self._theme
@property
def project(self) -> NWProject:
"""Return the active NWProject instance."""
if self._project is None:
raise Exception("SharedData class not fully initialised")
raise RuntimeError("SharedData class not fully initialised")
return self._project
@property
def spelling(self) -> NWSpellEnchant:
"""Return the active NWProject instance."""
if self._spelling is None:
raise Exception("SharedData class not fully initialised")
raise RuntimeError("SharedData class not fully initialised")
return self._spelling
@property
+14 -14
View File
@@ -333,7 +333,7 @@ class GuiManuscript(NToolDialog):
def _deleteSelectedBuild(self) -> None:
"""Delete the currently selected build settings entry."""
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):
dialog.close()
self._builds.removeBuild(build.buildID)
@@ -977,23 +977,23 @@ class _StatsWidget(QWidget):
def updateStats(self, data: dict[str, int]) -> None:
"""Update the stats values from a Tokenizer stats dict."""
# Minimal
self.minWordCount.setText("{0:n}".format(data.get(nwStats.WORDS, 0)))
self.minCharCount.setText("{0:n}".format(data.get(nwStats.CHARS, 0)))
self.minWordCount.setText(f"{data.get(nwStats.WORDS, 0):n}")
self.minCharCount.setText(f"{data.get(nwStats.CHARS, 0):n}")
# Maximal
self.maxTotalWords.setText("{0:n}".format(data.get(nwStats.WORDS, 0)))
self.maxHeadWords.setText("{0:n}".format(data.get(nwStats.WORDS_TITLE, 0)))
self.maxTextWords.setText("{0:n}".format(data.get(nwStats.WORDS_TEXT, 0)))
self.maxTitleCount.setText("{0:n}".format(data.get(nwStats.TITLES, 0)))
self.maxParCount.setText("{0:n}".format(data.get(nwStats.PARAGRAPHS, 0)))
self.maxTotalWords.setText(f"{data.get(nwStats.WORDS, 0):n}")
self.maxHeadWords.setText(f"{data.get(nwStats.WORDS_TITLE, 0):n}")
self.maxTextWords.setText(f"{data.get(nwStats.WORDS_TEXT, 0):n}")
self.maxTitleCount.setText(f"{data.get(nwStats.TITLES, 0):n}")
self.maxParCount.setText(f"{data.get(nwStats.PARAGRAPHS, 0):n}")
self.maxTotalChars.setText("{0:n}".format(data.get(nwStats.CHARS, 0)))
self.maxHeaderChars.setText("{0:n}".format(data.get(nwStats.CHARS_TITLE, 0)))
self.maxTextChars.setText("{0:n}".format(data.get(nwStats.CHARS_TEXT, 0)))
self.maxTotalChars.setText(f"{data.get(nwStats.CHARS, 0):n}")
self.maxHeaderChars.setText(f"{data.get(nwStats.CHARS_TITLE, 0):n}")
self.maxTextChars.setText(f"{data.get(nwStats.CHARS_TEXT, 0):n}")
self.maxTotalWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_ALL, 0)))
self.maxHeadWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_TITLE, 0)))
self.maxTextWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_TEXT, 0)))
self.maxTotalWordChars.setText(f"{data.get(nwStats.WCHARS_ALL, 0):n}")
self.maxHeadWordChars.setText(f"{data.get(nwStats.WCHARS_TITLE, 0):n}")
self.maxTextWordChars.setText(f"{data.get(nwStats.WCHARS_TEXT, 0):n}")
return
+4 -4
View File
@@ -228,8 +228,8 @@ class GuiBuildSettings(NToolDialog):
"""
if self._build.changed:
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:
self._emitBuildData()
self._build.resetChangedState()
@@ -1168,11 +1168,11 @@ class _FormattingTab(NScrollableForm):
for key, name in nwLabels.PAPER_NAME.items():
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.valueChanged.connect(self._pageSizeValueChanged)
self.pageHeight = NDoubleSpinBox(self, max=500.0)
self.pageHeight = NDoubleSpinBox(self, maxVal=500.0)
self.pageHeight.setFixedWidth(dbW)
self.pageHeight.valueChanged.connect(self._pageSizeValueChanged)
+2 -2
View File
@@ -447,7 +447,7 @@ class GuiWritingStats(NToolDialog):
rType = record.get("type")
if rType == "initial":
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":
try:
dStart = datetime.fromisoformat(str(record.get("start")))
@@ -570,7 +570,7 @@ class GuiWritingStats(NToolDialog):
idleEntry = formatTime(sIdle)
else:
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.setText(self.C_TIME, sStart)
+3 -3
View File
@@ -102,11 +102,11 @@ def cleanBuildDirs(args: argparse.Namespace) -> None:
if folder.is_dir():
try:
shutil.rmtree(folder)
print("Deleted: %s" % folder)
print(f"Deleted: {folder}")
except OSError:
print("Failed: %s" % folder)
print(f"Failed: {folder}")
else:
print("Missing: %s" % folder)
print(f"Missing: {folder}")
print("")
+32 -5
View File
@@ -55,17 +55,44 @@ force_grid_wrap = 0
lines_between_types = 1
forced_separate = ["tests.*"]
[tool.flake8]
max-line-length = 99
ignore = ["E133", "E221", "E226", "E228", "E241", "W503", "ANN101", "ANN102", "ANN401"]
per-file-ignores = ["tests/*:ANN"]
exclude = ["docs/*"]
[tool.ruff]
line-length = 99
[tool.ruff.lint]
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]
include = ["novelwriter"]
exclude = ["**/__pycache__"]
reportIncompatibleMethodOverride = false
reportGeneralTypeIssues = "information"
reportOptionalMemberAccess = "information"
reportOptionalOperand = "information"
reportOptionalSubscript = "information"
reportUnboundVariable = "error"
pythonVersion = "3.10"
+1 -4
View File
@@ -1,6 +1,3 @@
flake8
flake8-annotations
flake8-pep585
flake8-pyproject
ruff
isort
pyright
+1 -2
View File
@@ -195,8 +195,7 @@ def mockRnd(monkeypatch):
self.reset()
def _rnd(self, n):
for x in range(n):
yield x
yield from range(n)
def reset(self):
gen = self._rnd(1000)
+8 -8
View File
@@ -41,14 +41,14 @@ def testBaseSharedData_Init():
shared = SharedData()
# When not initialised, it should raise exceptions
with pytest.raises(Exception):
shared.mainGui
with pytest.raises(Exception):
shared.theme
with pytest.raises(Exception):
shared.project
with pytest.raises(Exception):
shared.spelling
with pytest.raises(RuntimeError):
_ = shared.mainGui
with pytest.raises(RuntimeError):
_ = shared.theme
with pytest.raises(RuntimeError):
_ = shared.project
with pytest.raises(RuntimeError):
_ = shared.spelling
# Create some mock objects
mockGui = MockGuiMain()
+3 -3
View File
@@ -885,9 +885,9 @@ def testCoreIndex_ExtractData(nwGUI, fncPath, mockRnd):
sHandle = project.newFile("Scene One", C.hNovelRoot)
tHandle = project.newFile("Scene Two", C.hNovelRoot)
project.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore
project.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore
project.tree[tHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore
assert project.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore
assert project.tree[sHandle].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(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)
docEditor = nwGUI.docEditor
docEditor.setPlainText("### Lorem Ipsum\n\n%s" % ipsumText[0])
docEditor.setPlainText(f"### Lorem Ipsum\n\n{ipsumText[0]}")
nwGUI.saveDocument()
# Check Defaults
@@ -147,7 +147,7 @@ def testGuiEditor_LoadText(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.openDocument(C.hSceneDoc) is True
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)
nwGUI.saveDocument()
nwGUI.closeDocument()
@@ -179,7 +179,7 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex
assert nwGUI.openDocument(C.hSceneDoc) is True
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)
# Missing item
@@ -483,7 +483,7 @@ def testGuiEditor_SpellChecking(qtbot, monkeypatch, nwGUI, projPath, ipsumText,
assert nwGUI.openDocument(C.hSceneDoc) is True
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)
# Toggle State
@@ -584,7 +584,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.openDocument(C.hSceneDoc) is True
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)
doc = docEditor.document()
@@ -650,7 +650,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Emphasis/Undo/Redo
# ==================
text = "### A Scene\n\n%s" % ipsumText[0]
text = f"### A Scene\n\n{ipsumText[0]}"
docEditor.replaceText(text)
# Emphasis
@@ -683,7 +683,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Shortcodes
# ==========
text = "### A Scene\n\n%s" % ipsumText[0]
text = f"### A Scene\n\n{ipsumText[0]}"
docEditor.replaceText(text)
# Italic
@@ -738,7 +738,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Quotes
# ======
text = "### A Scene\n\n%s" % ipsumText[0]
text = f"### A Scene\n\n{ipsumText[0]}"
docEditor.replaceText(text)
# Add Single Quotes
@@ -772,7 +772,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# 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)
docEditor.replaceText(repText)
assert docEditor.docAction(nwDocAction.RM_BREAKS) is True
@@ -974,7 +974,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True
docEditor = nwGUI.docEditor
text = "### A Scene\n\n%s" % ipsumText[0]
text = f"### A Scene\n\n{ipsumText[0]}"
# Insert Text
# ===========
@@ -1037,7 +1037,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
# Insert KeyWords
# ===============
text = "### A Scene\n\n\n%s" % ipsumText[0]
text = f"### A Scene\n\n\n{ipsumText[0]}"
docEditor.replaceText(text)
docEditor.setCursorLine(3)
@@ -1076,13 +1076,13 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.openDocument(C.hSceneDoc) is True
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)
# 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.setCursorPosition(45)
@@ -1114,7 +1114,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# 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
docEditor.replaceText(text)
@@ -1186,7 +1186,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# ==============
# 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.setCursorPosition(45)
docEditor._replaceQuotes("=", "<", ">")
@@ -1194,7 +1194,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# First Paragraph Selected
# 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.setCursorPosition(45)
assert docEditor.docAction(nwDocAction.SEL_PARA)
@@ -1222,7 +1222,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Check Blocks
cursor = docEditor.textCursor()
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.setCursorPosition(45)
assert len(docEditor._selectedBlocks(cursor)) == 0
@@ -1231,15 +1231,15 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert len(docEditor._selectedBlocks(cursor)) == 15
# 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.setCursorPosition(45)
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
# 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)
cursor = docEditor.textCursor()
cursor.setPosition(16, QtMoveAnchor)
@@ -1260,7 +1260,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# 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)
assert docEditor.getText() == text
@@ -1290,7 +1290,7 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, projPath, ipsumText
# Invalid and Generic
# ===================
text = "### A Scene\n\n%s" % ipsumText[0]
text = f"### A Scene\n\n{ipsumText[0]}"
docEditor.replaceText(text)
# Invalid Block
+10 -10
View File
@@ -276,11 +276,11 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
# Other Checks
# Replace Quotes
docEditor.setPlainText((
docEditor.setPlainText(
"### New Text\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n"
"Also text with \"double\" quotes which are \"less tricky\".\n\n"
))
)
mainMenu.aSelectAll.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
docEditor.setPlainText((
docEditor.setPlainText(
"### New Text\n\n"
"@char: Someone\n"
"@location: Somewhere\n\n"
"% Some comment ...\n\n"
"Here is some text\non multiple\nlines.\n\n"
"With another paragraph\nhere."
))
)
mainMenu.aFmtRmBreaks.activate(QAction.ActionEvent.Trigger)
assert docEditor.getText() == (
"### New Text\n\n"
@@ -317,14 +317,14 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"With another paragraph here.\n"
)
docEditor.setPlainText((
docEditor.setPlainText(
"### New Text\n\n"
"@char: Someone\n"
"@location: Somewhere\n\n"
"% Some comment ...\n\n"
"Here is some text\non multiple\nlines.\n\n"
"With another paragraph\nhere."
))
)
cursor = docEditor.textCursor()
cursor.setPosition(74)
cursor.movePosition(QtMoveRight, QtKeepAnchor, 29)
@@ -343,12 +343,12 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
assert not docEditor.docAction(nwDocAction.NO_ACTION)
# Test Invalid Formats
docEditor.setPlainText((
docEditor.setPlainText(
"### New Text\n\n"
"@tag: Bod\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n"
"Also text with \"double\" quotes which are \"less tricky\".\n\n"
))
)
# Cannot Format Tag
docEditor.setCursorPosition(17)
@@ -489,7 +489,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd
# 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")
action.activate(QAction.ActionEvent.Trigger)
assert docEditor.getText() == f"Stuff\n{key}: "
@@ -503,7 +503,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd
# 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)
docEditor.setPlainText("Stuff ")
docEditor.setCursorPosition(6)
+16 -16
View File
@@ -86,7 +86,7 @@ def testTextCounting_standardCounter():
assert standardCounter(" <") == (0, 0, 0)
# General Text
cC, wC, pC = standardCounter((
cC, wC, pC = standardCounter(
"#! Title\n\n"
"##! Prologue\n\n"
"# Heading One\n"
@@ -100,47 +100,47 @@ def testTextCounting_standardCounter():
"The second paragraph.\n\n\n"
"The third paragraph.\n\n"
"Dashes\u2013and even longer\u2014dashes."
))
)
assert cC == 163
assert wC == 26
assert pC == 4
# Text Alignment
cC, wC, pC = standardCounter((
cC, wC, pC = standardCounter(
"# Title\n\n"
"Left aligned<<\n\n"
"Left aligned <<\n\n"
"Right indent<\n\n"
"Right indent <\n\n"
))
)
assert cC == 53
assert wC == 9
assert pC == 4
cC, wC, pC = standardCounter((
cC, wC, pC = standardCounter(
"# Title\n\n"
">>Right aligned\n\n"
">> Right aligned\n\n"
">Left indent\n\n"
"> Left indent\n\n"
))
)
assert cC == 53
assert wC == 9
assert pC == 4
cC, wC, pC = standardCounter((
cC, wC, pC = standardCounter(
"# Title\n\n"
">>Centre aligned<<\n\n"
">> Centre aligned <<\n\n"
">Double indent<\n\n"
"> Double indent <\n\n"
))
)
assert cC == 59
assert wC == 9
assert pC == 4
# Formatting Codes, Upper Case (Old Implementation)
cC, wC, pC = standardCounter((
cC, wC, pC = standardCounter(
"Some text\n\n"
"[NEWPAGE]\n\n"
"more text\n\n"
@@ -150,13 +150,13 @@ def testTextCounting_standardCounter():
"and some final text\n\n"
"[VSPACE:4]\n\n"
"THE END\n\n"
))
)
assert cC == 58
assert wC == 13
assert pC == 5
# Formatting Codes, Lower Case (Current Implementation)
cC, wC, pC = standardCounter((
cC, wC, pC = standardCounter(
"Some text\n\n"
"[newpage]\n\n"
"more text\n\n"
@@ -166,16 +166,16 @@ def testTextCounting_standardCounter():
"and some final text\n\n"
"[vspace:4]\n\n"
"THE END\n\n"
))
)
assert cC == 58
assert wC == 13
assert pC == 5
# 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][i] nested [/i] emphasis [/b] in it.\n\n"
))
)
assert cC == 78
assert wC == 14
assert pC == 2
@@ -188,7 +188,7 @@ def testTextCounting_bodyTextCounter():
assert bodyTextCounter(None) == (0, 0, 0) # type: ignore
# General Text
wC, cC, sC = bodyTextCounter((
wC, cC, sC = bodyTextCounter(
"#! Title\n\n"
"##! Prologue\n\n"
"# Heading One\n"
@@ -201,7 +201,7 @@ def testTextCounting_bodyTextCounter():
"The second paragraph.\n\n\n"
"The third paragraph.\n\n"
"Dashes\u2013and even longer\u2014dashes."
))
)
assert wC == 14
assert cC == 91
assert sC == 81
+2 -2
View File
@@ -293,7 +293,7 @@ def testToolManuscript_Features(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
obj.close()
break
else:
assert False
raise AssertionError
# Finish
manus.close()
@@ -327,7 +327,7 @@ def testToolManuscript_Print(monkeypatch, qtbot, nwGUI, projPath):
obj.close()
break
else:
assert False
raise AssertionError
# Finish
manus.close()
+57 -57
View File
@@ -121,41 +121,41 @@ def testToolWritingStats_Export(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
# Sort by time
sessLog.listBox.sortByColumn(sessLog.C_TIME, Qt.SortOrder.AscendingOrder)
assert sessLog.novelWords.text() == "{:n}".format(600)
assert sessLog.notesWords.text() == "{:n}".format(275)
assert sessLog.totalWords.text() == "{:n}".format(875)
assert sessLog.novelWords.text() == f"{600:n}"
assert sessLog.notesWords.text() == f"{275:n}"
assert sessLog.totalWords.text() == f"{875:n}"
item = sessLog.listBox.topLevelItem(0)
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)
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)
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)
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)
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)
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)
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)
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_JSON)
@@ -230,35 +230,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0)
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)
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)
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)
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)
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)
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)
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)
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
qtbot.mouseClick(sessLog.incNovel, QtMouseLeft)
@@ -270,35 +270,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0)
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)
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)
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)
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)
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)
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)
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)
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 == [
{
@@ -339,35 +339,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0)
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)
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)
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)
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)
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)
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)
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)
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 == [
{
@@ -408,19 +408,19 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0)
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)
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)
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)
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 == [
{
@@ -449,43 +449,43 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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 == [
{
@@ -542,35 +542,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
item = sessLog.listBox.topLevelItem(0)
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)
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)
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)
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)
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)
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)
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)
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 == [
{
+7 -7
View File
@@ -97,18 +97,18 @@ def cmpFiles(
lnTwo = txtTwo[n].strip()
if n+1 in ignoreLines:
print("Ignoring line %d" % (n+1))
print(f"Ignoring line {n+1}")
continue
if ignoreStart is not None:
if lnOne.startswith(ignoreStart):
print("Ignoring line %d" % (n+1))
print(f"Ignoring line {n+1}")
continue
if lnOne != lnTwo:
print("Diff on line %d:" % (n+1))
print(" << '%s'" % lnOne)
print(" >> '%s'" % lnTwo)
print(f"Diff on line {n+1}:")
print(f" << '{lnOne}'")
print(f" >> '{lnTwo}'")
diffFound = True
return not diffFound
@@ -205,11 +205,11 @@ def buildTestProject(obj: object, projPath: Path) -> None:
project.index.reIndexHandle(tdHandle)
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)
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.session.startSession()
+2 -2
View File
@@ -102,7 +102,7 @@ def buildSampleZip(args: argparse.Namespace | None = None) -> None:
sys.exit(1)
print("")
print("Built file: %s" % dstSample)
print(f"Built file: {dstSample}")
print("")
return
@@ -242,7 +242,7 @@ def buildTranslationAssets(args: argparse.Namespace | None = None) -> None:
for item in srcDir.iterdir():
if item.is_file() and item.suffix == ".qm":
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("")
+3 -3
View File
@@ -65,12 +65,12 @@ def embedPython(bldDir: Path, outDir: Path) -> None:
"""Embed Python library."""
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"
pyZip = bldDir / zipFile
if not pyZip.is_file():
pyUrl = f"https://www.python.org/ftp/python/{pyVers}/{zipFile}"
print("Downloading: %s" % pyUrl)
print(f"Downloading: {pyUrl}")
urllib.request.urlretrieve(pyUrl, pyZip)
print("Extracting ...")
@@ -192,7 +192,7 @@ def main(args: argparse.Namespace) -> None:
print("")
numVers, _, _ = extractVersion()
print("Version: %s" % numVers)
print(f"Version: {numVers}")
bldDir = ROOT_DIR / "dist"
outDir = bldDir / "novelWriter"
+6 -6
View File
@@ -44,17 +44,17 @@ def extractVersion(beQuiet: bool = False) -> tuple[str, str, str]:
try:
for aLine in initFile.read_text(encoding="utf-8").splitlines():
if aLine.startswith("__version__"):
numVers = getValue((aLine))
numVers = getValue(aLine)
if aLine.startswith("__hexversion__"):
hexVers = getValue((aLine))
hexVers = getValue(aLine)
if aLine.startswith("__date__"):
relDate = getValue((aLine))
relDate = getValue(aLine)
except Exception as exc:
print("Could not read file: %s" % initFile)
print(f"Could not read file: {initFile}")
print(str(exc))
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
@@ -95,7 +95,7 @@ def copyPackageFiles(dst: Path, setupPy: bool = False) -> None:
copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"]
for copyFile in copyFiles:
shutil.copyfile(copyFile, dst / copyFile)
print("Copied: %s" % copyFile)
print(f"Copied: {copyFile}")
writeFile(dst / "MANIFEST.in", (
"include LICENSE.md\n"
+2 -2
View File
@@ -165,7 +165,7 @@ def _fixXml(svg: ET.Element) -> str:
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:
"""Write an icon theme file."""
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(f"meta:name = {name}\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("# Icons\n")
for key, svg in icons.items():