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
+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)