From 88c002f4eb03f66bb73218bc298bb98d496b2f26 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 11 Feb 2021 10:09:06 +1000 Subject: [PATCH] convert qt strings to f-strings with flynt Also revealed an incorrect type def in editor.py that mypy wasn't noticing before :-( --- qt/aqt/__init__.py | 8 +++---- qt/aqt/about.py | 12 +++++----- qt/aqt/addcards.py | 4 ++-- qt/aqt/addons.py | 24 +++++++++---------- qt/aqt/clayout.py | 10 ++++---- qt/aqt/customstudy.py | 2 +- qt/aqt/deckbrowser.py | 4 ++-- qt/aqt/editor.py | 40 +++++++++++++++---------------- qt/aqt/errors.py | 10 ++++---- qt/aqt/fields.py | 4 ++-- qt/aqt/importing.py | 10 ++++---- qt/aqt/main.py | 31 ++++++++++++------------ qt/aqt/mediasrv.py | 10 ++++---- qt/aqt/models.py | 2 +- qt/aqt/mpv.py | 30 +++++++++++------------ qt/aqt/overview.py | 8 +++---- qt/aqt/previewer.py | 4 ++-- qt/aqt/profiles.py | 2 +- qt/aqt/progress.py | 2 +- qt/aqt/reviewer.py | 55 ++++++++++++++++++++----------------------- qt/aqt/sidebar.py | 6 ++--- qt/aqt/sound.py | 8 +++---- qt/aqt/stats.py | 6 ++--- qt/aqt/studydeck.py | 2 +- qt/aqt/tagedit.py | 2 +- qt/aqt/taglimit.py | 8 +++---- qt/aqt/tts.py | 4 ++-- qt/aqt/utils.py | 28 ++++++++++------------ qt/aqt/webview.py | 12 +++++----- qt/aqt/winpaths.py | 2 +- 30 files changed, 169 insertions(+), 181 deletions(-) diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 040c643fd..e503c712b 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -232,7 +232,7 @@ def setupLangAndBackend( else: qt_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath) qt_lang = lang.replace("-", "_") - if _qtrans.load("qtbase_" + qt_lang, qt_dir): + if _qtrans.load(f"qtbase_{qt_lang}", qt_dir): app.installTranslator(_qtrans) return anki.lang.current_i18n @@ -249,7 +249,7 @@ class AnkiApp(QApplication): appMsg = pyqtSignal(str) - KEY = "anki" + checksum(getpass.getuser()) + KEY = f"anki{checksum(getpass.getuser())}" TMOUT = 30000 def __init__(self, argv: List[str]) -> None: @@ -319,7 +319,7 @@ def parseArgs(argv: List[str]) -> Tuple[argparse.Namespace, List[str]]: # as there's no such profile if isMac and len(argv) > 1 and argv[1].startswith("-psn"): argv = [argv[0]] - parser = argparse.ArgumentParser(description="Anki " + appVersion) + parser = argparse.ArgumentParser(description=f"Anki {appVersion}") parser.usage = "%(prog)s [OPTIONS] [file to import/add-on to install]" parser.add_argument("-b", "--base", help="path to base folder", default="") parser.add_argument("-p", "--profile", help="profile name to load", default="") @@ -424,7 +424,7 @@ def run() -> None: QMessageBox.critical( None, "Startup Error", - "Please notify support of this error:\n\n" + traceback.format_exc(), + f"Please notify support of this error:\n\n{traceback.format_exc()}", ) diff --git a/qt/aqt/about.py b/qt/aqt/about.py index 0948f4473..d49a21a36 100644 --- a/qt/aqt/about.py +++ b/qt/aqt/about.py @@ -81,7 +81,7 @@ def show(mw: aqt.AnkiQt) -> QDialog: (add-on provided name [Add-on folder, installed at, version, is config changed]) {newline.join(sorted(inactive))} """ - info = " " + " ".join(info.splitlines(True)) + info = f" {' '.join(info.splitlines(True))}" QApplication.clipboard().setText(info) tooltip(tr(TR.ABOUT_COPIED_TO_CLIPBOARD), parent=dialog) @@ -93,9 +93,9 @@ def show(mw: aqt.AnkiQt) -> QDialog: # WebView contents ###################################################################### abouttext = "
" - abouttext += "

" + tr(TR.ABOUT_ANKI_IS_A_FRIENDLY_INTELLIGENT_SPACED) - abouttext += "

" + tr(TR.ABOUT_ANKI_IS_LICENSED_UNDER_THE_AGPL3) - abouttext += "

" + tr(TR.ABOUT_VERSION, val=versionWithBuild()) + "
" + abouttext += f"

{tr(TR.ABOUT_ANKI_IS_A_FRIENDLY_INTELLIGENT_SPACED)}" + abouttext += f"

{tr(TR.ABOUT_ANKI_IS_LICENSED_UNDER_THE_AGPL3)}" + abouttext += f"

{tr(TR.ABOUT_VERSION, val=versionWithBuild())}
" abouttext += ("Python %s Qt %s PyQt %s
") % ( platform.python_version(), QT_VERSION_STR, @@ -211,8 +211,8 @@ def show(mw: aqt.AnkiQt) -> QDialog: abouttext += "

" + tr( TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES, cont=", ".join(allusers) ) - abouttext += "

" + tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE) - abouttext += "

" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE) + abouttext += f"

{tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE)}" + abouttext += f"

{tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE)}" abt.label.setMinimumWidth(800) abt.label.setMinimumHeight(600) dialog.show() diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 546e5f739..9f695ea20 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -84,7 +84,7 @@ class AddCards(QDialog): self.helpButton.setAutoDefault(False) bb.addButton(self.helpButton, QDialogButtonBox.HelpRole) # history - b = bb.addButton(tr(TR.ADDING_HISTORY) + " " + downArrow(), ar) + b = bb.addButton(f"{tr(TR.ADDING_HISTORY)} {downArrow()}", ar) if isMac: sc = "Ctrl+Shift+H" else: @@ -149,7 +149,7 @@ class AddCards(QDialog): fields = note.fields txt = htmlToTextLine(", ".join(fields)) if len(txt) > 30: - txt = txt[:30] + "..." + txt = f"{txt[:30]}..." line = tr(TR.ADDING_EDIT, val=txt) line = gui_hooks.addcards_will_add_history_entry(line, note) a = m.addAction(line) diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index bb208bb85..58a06fdc9 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -309,7 +309,7 @@ class AddonManager: meta = self.addon_meta(dir) name = meta.human_name() if not meta.enabled: - name += " " + tr(TR.ADDONS_DISABLED) + name += f" {tr(TR.ADDONS_DISABLED)}" return name # Conflict resolution @@ -741,11 +741,9 @@ class AddonsDialog(QDialog): name = addon.human_name() if not addon.enabled: - return name + " " + tr(TR.ADDONS_DISABLED2) + return f"{name} {tr(TR.ADDONS_DISABLED2)}" elif not addon.compatible(): - return ( - name + " " + tr(TR.ADDONS_REQUIRES, val=self.compatible_string(addon)) - ) + return f"{name} {tr(TR.ADDONS_REQUIRES, val=self.compatible_string(addon))}" return name @@ -818,7 +816,7 @@ class AddonsDialog(QDialog): if not addon: return if re.match(r"^\d+$", addon): - openLink(aqt.appShared + f"info/{addon}") + openLink(f"{aqt.appShared}info/{addon}") else: showWarning(tr(TR.ADDONS_ADDON_WAS_NOT_DOWNLOADED_FROM_ANKIWEB)) @@ -864,7 +862,7 @@ class AddonsDialog(QDialog): def onInstallFiles(self, paths: Optional[List[str]] = None) -> Optional[bool]: if not paths: - key = tr(TR.ADDONS_PACKAGED_ANKI_ADDON) + f" (*{self.mgr.ext})" + key = f"{tr(TR.ADDONS_PACKAGED_ANKI_ADDON)} (*{self.mgr.ext})" paths_ = getFile( self, tr(TR.ADDONS_INSTALL_ADDONS), None, key, key="addons", multi=True ) @@ -924,7 +922,7 @@ class GetAddons(QDialog): saveGeom(self, "getaddons") def onBrowse(self) -> None: - openLink(aqt.appShared + "addons/2.1") + openLink(f"{aqt.appShared}addons/2.1") def accept(self) -> None: # get codes @@ -946,7 +944,7 @@ def download_addon(client: HttpClient, id: int) -> Union[DownloadOk, DownloadErr "Fetch a single add-on from AnkiWeb." try: resp = client.get( - aqt.appShared + f"download/{id}?v=2.1&p={current_point_version}" + f"{aqt.appShared}download/{id}?v=2.1&p={current_point_version}" ) if resp.status_code != 200: return DownloadError(status_code=resp.status_code) @@ -1107,7 +1105,7 @@ def show_log_to_user(parent: QWidget, log: List[DownloadLogEntry]) -> None: text = tr(TR.ADDONS_ONE_OR_MORE_ERRORS_OCCURRED) else: text = tr(TR.ADDONS_DOWNLOAD_COMPLETE_PLEASE_RESTART_ANKI_TO) - text += "

" + download_log_to_html(log) + text += f"

{download_log_to_html(log)}" if have_problem: showWarning(text, textFormat="rich", parent=parent) @@ -1153,7 +1151,7 @@ def _fetch_update_info_batch( """Get update info from AnkiWeb. Chunk must not contain more than 25 ids.""" - resp = client.get(aqt.appShared + "updates/" + ",".join(chunk) + "?v=3") + resp = client.get(f"{aqt.appShared}updates/{','.join(chunk)}?v=3") if resp.status_code == 200: return resp.json() else: @@ -1366,7 +1364,7 @@ class ConfigEditor(QDialog): showInfo(msg) return except Exception as e: - showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + " " + repr(e)) + showInfo(f"{tr(TR.ADDONS_INVALID_CONFIGURATION)} {repr(e)}") return if not isinstance(new_conf, dict): @@ -1419,7 +1417,7 @@ def installAddonPackages( if log: log_html = "
".join(log) if advise_restart: - log_html += "

" + tr(TR.ADDONS_PLEASE_RESTART_ANKI_TO_COMPLETE_THE) + log_html += f"

{tr(TR.ADDONS_PLEASE_RESTART_ANKI_TO_COMPLETE_THE)}" if len(log) == 1 and not strictly_modal: tooltip(log_html, parent=parent) else: diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index 041e4f031..7d0a38be2 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -115,7 +115,7 @@ class CardLayout(QDialog): self.topAreaForm = aqt.forms.clayout_top.Ui_Form() self.topAreaForm.setupUi(self.topArea) self.topAreaForm.templateOptions.setText( - tr(TR.ACTIONS_OPTIONS) + " " + downArrow() + f"{tr(TR.ACTIONS_OPTIONS)} {downArrow()}" ) qconnect(self.topAreaForm.templateOptions.clicked, self.onMore) qconnect( @@ -305,7 +305,7 @@ class CardLayout(QDialog): qconnect(pform.preview_front.clicked, self.on_preview_toggled) qconnect(pform.preview_back.clicked, self.on_preview_toggled) pform.preview_settings.setText( - tr(TR.CARD_TEMPLATES_PREVIEW_SETTINGS) + " " + downArrow() + f"{tr(TR.CARD_TEMPLATES_PREVIEW_SETTINGS)} {downArrow()}" ) qconnect(pform.preview_settings.clicked, self.on_preview_settings) @@ -491,7 +491,7 @@ class CardLayout(QDialog): text = a # use _showAnswer to avoid the longer delay - self.preview_web.eval("_showAnswer(%s,'%s');" % (json.dumps(text), bodyclass)) + self.preview_web.eval(f"_showAnswer({json.dumps(text)},'{bodyclass}');") self.preview_web.eval( f"_emulateMobile({json.dumps(self.mobile_emulation_enabled)});" ) @@ -522,14 +522,14 @@ class CardLayout(QDialog): def answerRepl(match: Match) -> str: res = self.mw.reviewer.correct("exomple", "an example") if hadHR: - res = "


" + res + res = f"
{res}" return res repl: Union[str, Callable] if type == "q": repl = "" - repl = "
%s
" % repl + repl = f"
{repl}
" else: repl = answerRepl return re.sub(r"\[\[type:.+?\]\]", repl, txt) diff --git a/qt/aqt/customstudy.py b/qt/aqt/customstudy.py index 8dade1388..e4bc605dc 100644 --- a/qt/aqt/customstudy.py +++ b/qt/aqt/customstudy.py @@ -59,7 +59,7 @@ class CustomStudy(QDialog): def plus(num: Union[int, str]) -> str: if num == 1000: num = "1000+" - return "" + str(num) + "" + return f"{str(num)}" if idx == RADIO_NEW: new = self.mw.col.sched.totalNewForCurrentDeck() diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index 4cb0ae697..de69648b8 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -288,7 +288,7 @@ class DeckBrowser: extra = tr(TR.DECKS_IT_HAS_CARD, count=count) if askUser( - tr(TR.DECKS_ARE_YOU_SURE_YOU_WISH_TO, val=deck["name"]) + " " + extra + f"{tr(TR.DECKS_ARE_YOU_SURE_YOU_WISH_TO, val=deck['name'])} {extra}" ): return True return False @@ -332,4 +332,4 @@ class DeckBrowser: ) def _onShared(self) -> None: - openLink(aqt.appShared + "decks/") + openLink(f"{aqt.appShared}decks/") diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index cd37a25ed..ee0fe2e47 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -130,7 +130,7 @@ class Editor: None, "fields", tr(TR.EDITING_CUSTOMIZE_FIELDS), - tr(TR.EDITING_FIELDS) + "...", + f"{tr(TR.EDITING_FIELDS)}...", disables=False, rightside=False, ), @@ -138,7 +138,7 @@ class Editor: None, "cards", tr(TR.EDITING_CUSTOMIZE_CARD_TEMPLATES_CTRLANDL), - tr(TR.EDITING_CARDS) + "...", + f"{tr(TR.EDITING_CARDS)}...", disables=False, rightside=False, ), @@ -239,7 +239,7 @@ class Editor: with open(path, "rb") as fp: data = fp.read() data64 = b"".join(base64.encodebytes(data).splitlines()) - return "data:%s;base64,%s" % (mime, data64.decode("ascii")) + return f"data:{mime};base64,{data64.decode('ascii')}" def addButton( self, @@ -314,7 +314,7 @@ class Editor: else: imgelm = "" if label or not imgelm: - labelelm = """{}""".format(label or cmd) + labelelm = f"""{label or cmd}""" else: labelelm = "" if id: @@ -539,7 +539,7 @@ class Editor: if err == 2: cols[0] = "dupe" - self.web.eval("setBackgrounds(%s);" % json.dumps(cols)) + self.web.eval(f"setBackgrounds({json.dumps(cols)});") def showDupes(self) -> None: aqt.dialogs.open( @@ -734,23 +734,23 @@ class Editor: self._wrapWithColour(self.fcolour) def _updateForegroundButton(self) -> None: - self.web.eval("setFGButton('%s')" % self.fcolour) + self.web.eval(f"setFGButton('{self.fcolour}')") def onColourChanged(self) -> None: self._updateForegroundButton() self.mw.pm.profile["lastColour"] = self.fcolour def _wrapWithColour(self, colour: str) -> None: - self.web.eval("setFormat('forecolor', '%s')" % colour) + self.web.eval(f"setFormat('forecolor', '{colour}')") # Audio/video/images ###################################################################### def onAddMedia(self) -> None: extension_filter = " ".join( - "*." + extension for extension in sorted(itertools.chain(pics, audio)) + f"*.{extension}" for extension in sorted(itertools.chain(pics, audio)) ) - key = tr(TR.EDITING_MEDIA) + " (" + extension_filter + ")" + key = f"{tr(TR.EDITING_MEDIA)} ({extension_filter})" def accept(file: str) -> None: self.addMedia(file, canDelete=True) @@ -770,7 +770,7 @@ class Editor: except Exception as e: showWarning(str(e)) return - self.web.eval("setFormat('inserthtml', %s);" % json.dumps(html)) + self.web.eval(f"setFormat('inserthtml', {json.dumps(html)});") def _addMedia(self, path: str, canDelete: bool = False) -> str: "Add to media folder and return local img or sound tag." @@ -810,15 +810,15 @@ class Editor: ext = fname.split(".")[-1].lower() if ext in pics: name = urllib.parse.quote(fname.encode("utf8")) - return '' % name + return f'' else: av_player.play_file(fname) - return "[sound:%s]" % html.escape(fname, quote=False) + return f"[sound:{html.escape(fname, quote=False)}]" def urlToFile(self, url: str) -> Optional[str]: l = url.lower() for suffix in pics + audio: - if l.endswith("." + suffix): + if l.endswith(f".{suffix}"): return self._retrieveURL(url) # not a supported type return None @@ -842,7 +842,7 @@ class Editor: data = base64.b64decode(b64data, validate=True) if ext == "jpeg": ext = "jpg" - return self._addPastedImage(data, "." + ext) + return self._addPastedImage(data, f".{ext}") return "" @@ -857,7 +857,7 @@ class Editor: def _addPastedImage(self, data: bytes, ext: str) -> str: # hash and write csum = checksum(data) - fname = "{}-{}{}".format("paste", csum, ext) + fname = f"paste-{csum}{ext}" return self._addMediaFromData(fname, data) def _retrieveURL(self, url: str) -> Optional[str]: @@ -967,9 +967,7 @@ class Editor: ext = "true" else: ext = "false" - self.web.eval( - "pasteHTML(%s, %s, %s);" % (json.dumps(html), json.dumps(internal), ext) - ) + self.web.eval(f"pasteHTML({json.dumps(html)}, {json.dumps(internal)}, {ext});") def doDrop(self, html: str, internal: bool, extended: bool = False) -> None: def pasteIfField(ret: bool) -> None: @@ -1187,8 +1185,8 @@ class EditorWebView(AnkiWebView): token = html.escape(token).replace("\t", " " * 4) # if there's more than one consecutive space, # use non-breaking spaces for the second one on - def repl(match: Match) -> None: - return match.group(1).replace(" ", " ") + " " + def repl(match: Match) -> str: + return f"{match.group(1).replace(' ', ' ')} " token = re.sub(" ( +)", repl, token) processed.append(token) @@ -1247,7 +1245,7 @@ class EditorWebView(AnkiWebView): if not mime.hasHtml(): return html = mime.html() - mime.setHtml("" + html) + mime.setHtml(f"{html}") clip.setMimeData(mime) def contextMenuEvent(self, evt: QContextMenuEvent) -> None: diff --git a/qt/aqt/errors.py b/qt/aqt/errors.py index fc40eaa3e..51e5ca14b 100644 --- a/qt/aqt/errors.py +++ b/qt/aqt/errors.py @@ -95,13 +95,13 @@ class ErrorHandler(QObject): if self.mw.addonManager.dirty: txt = markdown(tr(TR.ERRORS_ADDONS_ACTIVE_POPUP)) - error = supportText() + self._addonText(error) + "\n" + error + error = f"{supportText() + self._addonText(error)}\n{error}" else: txt = markdown(tr(TR.ERRORS_STANDARD_POPUP)) - error = supportText() + "\n" + error + error = f"{supportText()}\n{error}" # show dialog - txt = txt + "
" + error + "
" + txt = f"{txt}
{error}
" showText(txt, type="html", copyBtn=True) def _addonText(self, error: str) -> str: @@ -113,6 +113,6 @@ class ErrorHandler(QObject): mw.addonManager.addonName(i) for i in dict.fromkeys(reversed(matches)) ] # highlight importance of first add-on: - addons[0] = "{}".format(addons[0]) + addons[0] = f"{addons[0]}" addons_str = ", ".join(addons) - return tr(TR.ADDONS_POSSIBLY_INVOLVED, addons=addons_str) + "\n" + return f"{tr(TR.ADDONS_POSSIBLY_INVOLVED, addons=addons_str)}\n" diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py index 8cbbb29e1..e19668842 100644 --- a/qt/aqt/fields.py +++ b/qt/aqt/fields.py @@ -60,7 +60,7 @@ class FieldDialog(QDialog): self.currentIdx = None self.form.fieldList.clear() for c, f in enumerate(self.model["flds"]): - self.form.fieldList.addItem("{}: {}".format(c + 1, f["name"])) + self.form.fieldList.addItem(f"{c + 1}: {f['name']}") def setupSignals(self) -> None: f = self.form @@ -244,7 +244,7 @@ class FieldDialog(QDialog): fut.result() except TemplateError as e: # fixme: i18n - showWarning("Unable to save changes: " + str(e)) + showWarning(f"Unable to save changes: {str(e)}") return self.mw.reset() tooltip("Changes saved.", parent=self.mw) diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py index 99db0bda7..d1cecc330 100644 --- a/qt/aqt/importing.py +++ b/qt/aqt/importing.py @@ -194,7 +194,7 @@ class ImportDialog(QDialog): showUnicodeWarning() return except Exception as e: - msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n" + msg = f"{tr(TR.IMPORTING_FAILED_DEBUG_INFO)}\n" err = repr(str(e)) if "1-character string" in err: msg += err @@ -205,7 +205,7 @@ class ImportDialog(QDialog): showText(msg) return else: - txt = tr(TR.IMPORTING_IMPORTING_COMPLETE) + "\n" + txt = f"{tr(TR.IMPORTING_IMPORTING_COMPLETE)}\n" if self.importer.log: txt += "\n".join(self.importer.log) self.close() @@ -326,7 +326,7 @@ def importFile(mw: AnkiQt, file: str) -> None: if done: break for mext in re.findall(r"[( ]?\*\.(.+?)[) ]", i[0]): - if file.endswith("." + mext): + if file.endswith(f".{mext}"): importerClass = i[1] done = True break @@ -352,7 +352,7 @@ def importFile(mw: AnkiQt, file: str) -> None: if msg == "'unknownFormat'": showWarning(tr(TR.IMPORTING_UNKNOWN_FILE_FORMAT)) else: - msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n" + msg = f"{tr(TR.IMPORTING_FAILED_DEBUG_INFO)}\n" msg += str(traceback.format_exc()) showText(msg) return @@ -392,7 +392,7 @@ def importFile(mw: AnkiQt, file: str) -> None: elif "readonly" in err: showWarning(tr(TR.IMPORTING_UNABLE_TO_IMPORT_FROM_A_READONLY)) else: - msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n" + msg = f"{tr(TR.IMPORTING_FAILED_DEBUG_INFO)}\n" msg += str(traceback.format_exc()) showText(msg) else: diff --git a/qt/aqt/main.py b/qt/aqt/main.py index a774b1064..00217ff82 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -397,7 +397,7 @@ class AnkiQt(QMainWindow): restoreGeom(self, "mainWindow") restoreState(self, "mainWindow") # titlebar - self.setWindowTitle(self.pm.name + " - Anki") + self.setWindowTitle(f"{self.pm.name} - Anki") # show and raise window for osx self.show() self.activateWindow() @@ -489,7 +489,7 @@ class AnkiQt(QMainWindow): ) else: showWarning( - tr(TR.ERRORS_UNABLE_OPEN_COLLECTION) + "\n" + traceback.format_exc() + f"{tr(TR.ERRORS_UNABLE_OPEN_COLLECTION)}\n{traceback.format_exc()}" ) # clean up open collection if possible try: @@ -643,14 +643,14 @@ class AnkiQt(QMainWindow): def moveToState(self, state: str, *args: Any) -> None: # print("-> move from", self.state, "to", state) oldState = self.state or "dummy" - cleanup = getattr(self, "_" + oldState + "Cleanup", None) + cleanup = getattr(self, f"_{oldState}Cleanup", None) if cleanup: # pylint: disable=not-callable cleanup(state) self.clearStateShortcuts() self.state = state gui_hooks.state_will_change(state, oldState) - getattr(self, "_" + state + "State")(oldState, *args) + getattr(self, f"_{state}State")(oldState, *args) if state != "resetRequired": self.bottomWeb.show() gui_hooks.state_did_change(state, oldState) @@ -730,14 +730,13 @@ class AnkiQt(QMainWindow): i = tr(TR.QT_MISC_WAITING_FOR_EDITING_TO_FINISH) b = self.button("refresh", tr(TR.QT_MISC_RESUME_NOW), id="resume") self.web.stdHtml( - """ -
+ f""" +
-%s

-%s
+{i}

+{b}
-""" - % (i, b), +""", context=web_context, ) self.bottomWeb.hide() @@ -755,7 +754,7 @@ class AnkiQt(QMainWindow): id: str = "", extra: str = "", ) -> str: - class_ = "but " + class_ + class_ = f"but {class_}" if key: key = tr(TR.ACTIONS_SHORTCUT_KEY, val=key) else: @@ -989,7 +988,7 @@ title="%s" %s>%s""" % ( def setStateShortcuts(self, shortcuts: List[Tuple[str, Callable]]) -> None: gui_hooks.state_shortcuts_will_change(self.state, shortcuts) # legacy hook - runHook(self.state + "StateShortcuts", shortcuts) + runHook(f"{self.state}StateShortcuts", shortcuts) self.stateShortcuts = self.applyShortcuts(shortcuts) def clearStateShortcuts(self) -> None: @@ -1285,7 +1284,7 @@ title="%s" %s>%s""" % ( if not existed: f.write(b"nid\tmid\tfields\n") for id, mid, flds in col.db.execute( - "select id, mid, flds from notes where id in %s" % ids2str(nids) + f"select id, mid, flds from notes where id in {ids2str(nids)}" ): fields = splitFields(flds) f.write(("\t".join([str(id), str(mid)] + fields)).encode("utf8")) @@ -1471,9 +1470,9 @@ title="%s" %s>%s""" % ( buf = "" for c, line in enumerate(text.strip().split("\n")): if c == 0: - buf += ">>> %s\n" % line + buf += f">>> {line}\n" else: - buf += "... %s\n" % line + buf += f"... {line}\n" try: to_append = buf + (self._output or "") to_append = gui_hooks.debug_console_did_evaluate_python( @@ -1616,7 +1615,7 @@ title="%s" %s>%s""" % ( self.mediaServer.start() def baseHTML(self) -> str: - return '' % self.serverURL() + return f'' def serverURL(self) -> str: return "http://127.0.0.1:%d/" % self.mediaServer.getPort() diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 923200062..9f3177a6e 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -33,7 +33,7 @@ def _getExportFolder() -> str: return webInSrcFolder elif isMac: dir = os.path.dirname(os.path.abspath(__file__)) - return os.path.abspath(dir + "/../../Resources/web") + return os.path.abspath(f"{dir}/../../Resources/web") else: if os.environ.get("TEST_TARGET"): # running tests in bazel; we have no data @@ -110,7 +110,7 @@ def allroutes(pathin: str) -> Response: isdir = os.path.isdir(os.path.join(directory, path)) except ValueError: return flask.make_response( - "Path for '%s - %s' is too long!" % (directory, path), + f"Path for '{directory} - {path}' is too long!", HTTPStatus.BAD_REQUEST, ) @@ -121,13 +121,13 @@ def allroutes(pathin: str) -> Response: # protect against directory transversal: https://security.openstack.org/guidelines/dg_using-file-paths.html if not fullpath.startswith(directory): return flask.make_response( - "Path for '%s - %s' is a security leak!" % (directory, path), + f"Path for '{directory} - {path}' is a security leak!", HTTPStatus.FORBIDDEN, ) if isdir: return flask.make_response( - "Path for '%s - %s' is a directory (not supported)!" % (directory, path), + f"Path for '{directory} - {path}' is a directory (not supported)!", HTTPStatus.FORBIDDEN, ) @@ -221,7 +221,7 @@ def _redirectWebExports(path: str) -> Tuple[str, str]: addMgr = aqt.mw.addonManager except AttributeError as error: if devMode: - print("_redirectWebExports: %s" % error) + print(f"_redirectWebExports: {error}") return None try: diff --git a/qt/aqt/models.py b/qt/aqt/models.py index 634e21a33..e03a08fe3 100644 --- a/qt/aqt/models.py +++ b/qt/aqt/models.py @@ -128,7 +128,7 @@ class Models(QDialog): self.models = notetypes for m in self.models: mUse = tr(TR.BROWSING_NOTE_COUNT, count=m.use_count) - item = QListWidgetItem("%s [%s]" % (m.name, mUse)) + item = QListWidgetItem(f"{m.name} [{mUse}]") self.form.modelsList.addItem(item) self.form.modelsList.setCurrentRow(row) diff --git a/qt/aqt/mpv.py b/qt/aqt/mpv.py index 94556f1ad..8bffad40f 100644 --- a/qt/aqt/mpv.py +++ b/qt/aqt/mpv.py @@ -119,9 +119,9 @@ class MPVBase: """Prepare the argument list for the mpv process.""" self.argv = [self.executable] self.argv += self.default_argv - self.argv += ["--input-ipc-server=" + self._sock_filename] + self.argv += [f"--input-ipc-server={self._sock_filename}"] if self.window_id is not None: - self.argv += ["--wid=" + str(self.window_id)] + self.argv += [f"--wid={str(self.window_id)}"] def _start_process(self): """Start the mpv process.""" @@ -258,7 +258,7 @@ class MPVBase: buf = buf[newline + 1 :] if self.debug: - sys.stdout.write("<<< " + data.decode("utf8", "replace")) + sys.stdout.write(f"<<< {data.decode('utf8', 'replace')}") message = self._parse_message(data) self._handle_message(message) @@ -298,7 +298,7 @@ class MPVBase: self._event_queue.put(message) else: - raise MPVCommunicationError("invalid message %r" % message) + raise MPVCommunicationError(f"invalid message {message!r}") def _send_message(self, message, timeout=None): """Send a message/command to the mpv process, message must be a @@ -308,7 +308,7 @@ class MPVBase: data = self._compose_message(message) if self.debug: - sys.stdout.write(">>> " + data.decode("utf8", "replace")) + sys.stdout.write(f">>> {data.decode('utf8', 'replace')}") # Request/response cycles are coordinated across different threads, so # that they don't get mixed up. This makes it possible to use commands @@ -371,7 +371,7 @@ class MPVBase: self._send_message(message, timeout) return self._get_response(timeout) except MPVCommandError as e: - raise MPVCommandError("%r: %s" % (message["command"], e)) + raise MPVCommandError(f"{message['command']!r}: {e}") except Exception as e: if _retry: print("mpv timed out, restarting") @@ -512,7 +512,7 @@ class MPV(MPVBase): return if message["event"] == "property-change": - name = "property-" + message["name"] + name = f"property-{message['name']}" else: name = message["event"] @@ -527,7 +527,7 @@ class MPV(MPVBase): try: self.command("enable_event", name) except MPVCommandError: - raise MPVError("no such event %r" % name) + raise MPVError(f"no such event {name!r}") self._callbacks.setdefault(name, []).append(callback) @@ -538,12 +538,12 @@ class MPV(MPVBase): try: callbacks = self._callbacks[name] except KeyError: - raise MPVError("no callbacks registered for event %r" % name) + raise MPVError(f"no callbacks registered for event {name!r}") try: callbacks.remove(callback) except ValueError: - raise MPVError("callback %r not registered for event %r" % (callback, name)) + raise MPVError(f"callback {callback!r} not registered for event {name!r}") def register_property_callback(self, name, callback): """Register a function `callback` for the property-change event on @@ -556,9 +556,9 @@ class MPV(MPVBase): # Apparently observe_property does not check it :-( proplist = self.command("get_property", "property-list") if name not in proplist: - raise MPVError("no such property %r" % name) + raise MPVError(f"no such property {name!r}") - self._callbacks.setdefault("property-" + name, []).append(callback) + self._callbacks.setdefault(f"property-{name}", []).append(callback) # 'observe_property' expects some kind of id which can be used later # for unregistering with 'unobserve_property'. @@ -572,15 +572,15 @@ class MPV(MPVBase): property-change event on property `name`. """ try: - callbacks = self._callbacks["property-" + name] + callbacks = self._callbacks[f"property-{name}"] except KeyError: - raise MPVError("no callbacks registered for property %r" % name) + raise MPVError(f"no callbacks registered for property {name!r}") try: callbacks.remove(callback) except ValueError: raise MPVError( - "callback %r not registered for property %r" % (callback, name) + f"callback {callback!r} not registered for property {name!r}" ) serial = self._property_serials.pop((name, callback)) diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index 949c2e8c1..723528602 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -80,7 +80,7 @@ class Overview: elif url == "decks": self.mw.moveToState("deckBrowser") elif url == "review": - openLink(aqt.appShared + "info/%s?v=%s" % (self.sid, self.sidVer)) + openLink(f"{aqt.appShared}info/{self.sid}?v={self.sidVer}") elif url == "studymore" or url == "customStudy": self.onStudyMore() elif url == "unbury": @@ -180,8 +180,8 @@ class Overview: def _desc(self, deck: Dict[str, Any]) -> str: if deck["dyn"]: desc = tr(TR.STUDYING_THIS_IS_A_SPECIAL_DECK_FOR) - desc += " " + tr(TR.STUDYING_CARDS_WILL_BE_AUTOMATICALLY_RETURNED_TO) - desc += " " + tr(TR.STUDYING_DELETING_THIS_DECK_FROM_THE_DECK) + desc += f" {tr(TR.STUDYING_CARDS_WILL_BE_AUTOMATICALLY_RETURNED_TO)}" + desc += f" {tr(TR.STUDYING_DELETING_THIS_DECK_FROM_THE_DECK)}" else: desc = deck.get("desc", "") if deck.get("md", False): @@ -192,7 +192,7 @@ class Overview: dyn = "dyn" else: dyn = "" - return '
%s
' % (dyn, desc) + return f'
{desc}
' def _table(self) -> Optional[str]: counts = list(self.mw.col.sched.counts()) diff --git a/qt/aqt/previewer.py b/qt/aqt/previewer.py index fc5718c6d..0e6a1dc98 100644 --- a/qt/aqt/previewer.py +++ b/qt/aqt/previewer.py @@ -214,9 +214,9 @@ class Previewer(QDialog): av_player.clear_queue_and_maybe_interrupt() txt = self.mw.prepare_card_text_for_display(txt) - txt = gui_hooks.card_will_show(txt, c, "preview" + self._state.capitalize()) + txt = gui_hooks.card_will_show(txt, c, f"preview{self._state.capitalize()}") self._last_state = self._state_and_mod() - self._web.eval("{}({},'{}');".format(func, json.dumps(txt), bodyclass)) + self._web.eval(f"{func}({json.dumps(txt)},'{bodyclass}');") self._card_changed = False def _on_show_both_sides(self, toggle: bool) -> None: diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index bf540a84b..8c5ee1018 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -522,7 +522,7 @@ create table if not exists profiles without_unicode_isolation( tr( TR.PROFILES_FOLDER_README, - link=appHelpSite + "files?id=startup-options", + link=f"{appHelpSite}files?id=startup-options", ) ) ) diff --git a/qt/aqt/progress.py b/qt/aqt/progress.py index 098f64d85..d5002208e 100644 --- a/qt/aqt/progress.py +++ b/qt/aqt/progress.py @@ -45,7 +45,7 @@ class ProgressManager: def handler() -> None: if requiresCollection and not self.mw.col: # no current collection; timer is no longer valid - print("Ignored progress func as collection unloaded: %s" % repr(func)) + print(f"Ignored progress func as collection unloaded: {repr(func)}") return if not self._levels: diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 69b692eb7..3702d2db5 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -97,9 +97,7 @@ class Reviewer: mins = int(round(elapsed[0] / 60)) part2 = tr(TR.STUDYING_MINUTE, count=mins) fin = tr(TR.STUDYING_FINISH) - diag = askUserDialog( - "%s %s" % (part1, part2), [tr(TR.STUDYING_CONTINUE), fin] - ) + diag = askUserDialog(f"{part1} {part2}", [tr(TR.STUDYING_CONTINUE), fin]) diag.setIcon(QMessageBox.Information) if diag.run() == fin: return self.mw.moveToState("deckBrowser") @@ -142,15 +140,13 @@ class Reviewer: fade = "" if self.mw.pm.video_driver() == VideoDriver.Software: fade = "" - return """ + return f"""
-{} +{fade}
-{} -""".format( - fade, extra - ) +{extra} +""" def _initWeb(self) -> None: self._reps = 0 @@ -207,7 +203,7 @@ class Reviewer: bodyclass = theme_manager.body_classes_for_card_ord(c.ord) - self.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass)) + self.web.eval(f"_showQuestion({json.dumps(q)},'{bodyclass}');") self._drawFlag() self._drawMark() self._showAnswerButton() @@ -220,10 +216,10 @@ class Reviewer: return card.autoplay() def _drawFlag(self) -> None: - self.web.eval("_drawFlag(%s);" % self.card.userFlag()) + self.web.eval(f"_drawFlag({self.card.userFlag()});") def _drawMark(self) -> None: - self.web.eval("_drawMark(%s);" % json.dumps(self.card.note().hasTag("marked"))) + self.web.eval(f"_drawMark({json.dumps(self.card.note().hasTag('marked'))});") # Showing the answer ########################################################################## @@ -248,7 +244,7 @@ class Reviewer: a = self._mungeQA(a) a = gui_hooks.card_will_show(a, c, "reviewAnswer") # render and update bottom - self.web.eval("_showAnswer(%s);" % json.dumps(a)) + self.web.eval(f"_showAnswer({json.dumps(a)});") self._showEaseButtons() self.mw.web.setFocus() # user hook @@ -399,13 +395,12 @@ class Reviewer: return re.sub(self.typeAnsPat, "", buf) return re.sub( self.typeAnsPat, - """ + f"""
+ style="font-family: '{self.typeFont}'; font-size: {self.typeSize}px;">
-""" - % (self.typeFont, self.typeSize), +""", buf, ) @@ -440,7 +435,7 @@ class Reviewer: if hadHR: # a hack to ensure the q/a separator falls before the answer # comparison when user is using {{FrontSide}} - s = "
" + s + s = f"
{s}" return s return re.sub(self.typeAnsPat, repl, buf) @@ -506,13 +501,13 @@ class Reviewer: givenElems, correctElems = self.tokenizeComparison(given, correct) def good(s: str) -> str: - return "" + html.escape(s) + "" + return f"{html.escape(s)}" def bad(s: str) -> str: - return "" + html.escape(s) + "" + return f"{html.escape(s)}" def missed(s: str) -> str: - return "" + html.escape(s) + "" + return f"{html.escape(s)}" if given == correct: res = good(given) @@ -531,14 +526,14 @@ class Reviewer: res += good(txt) else: res += missed(txt) - res = "
" + res + "
" + res = f"
{res}
" return res def _noLoneMarks(self, s: str) -> str: # ensure a combining character at the start does not join to # previous text if s and ucd.category(s[0]).startswith("M"): - return "\xa0" + s + return f"\xa0{s}" return s def _getTypedAnswer(self) -> None: @@ -602,7 +597,7 @@ time = %(time)d; def _showEaseButtons(self) -> None: middle = self._answerButtons() - self.bottom.web.eval("showAnswer(%s);" % json.dumps(middle)) + self.bottom.web.eval(f"showAnswer({json.dumps(middle)});") def _remaining(self) -> str: if not self.mw.col.conf["dueCounts"]: @@ -613,11 +608,11 @@ time = %(time)d; else: counts = list(self.mw.col.sched.counts(self.card)) idx = self.mw.col.sched.countIdx(self.card) - counts[idx] = "%s" % (counts[idx]) + counts[idx] = f"{counts[idx]}" space = " + " - ctxt = "%s" % counts[0] - ctxt += space + "%s" % counts[1] - ctxt += space + "%s" % counts[2] + ctxt = f"{counts[0]}" + ctxt += f"{space}{counts[1]}" + ctxt += f"{space}{counts[2]}" return ctxt def _defaultEase(self) -> int: @@ -683,7 +678,7 @@ time = %(time)d; if not self.mw.col.conf["estTimes"]: return "
" txt = self.mw.col.sched.nextIvlStr(self.card, i, True) or " " - return "%s
" % txt + return f"{txt}
" # Leeches ########################################################################## @@ -692,7 +687,7 @@ time = %(time)d; # for now s = tr(TR.STUDYING_CARD_WAS_A_LEECH) if card.queue < 0: - s += " " + tr(TR.STUDYING_IT_HAS_BEEN_SUSPENDED) + s += f" {tr(TR.STUDYING_IT_HAS_BEEN_SUSPENDED)}" tooltip(s) # Context menu diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py index da4fed791..e3ddb63e1 100644 --- a/qt/aqt/sidebar.py +++ b/qt/aqt/sidebar.py @@ -805,7 +805,7 @@ class SidebarTreeView(QTreeView): full_name=head + node.name, ) root.add_child(item) - newhead = head + node.name + "::" + newhead = f"{head + node.name}::" render(item, node.children, newhead) tree = self.col.tags.tree() @@ -854,7 +854,7 @@ class SidebarTreeView(QTreeView): full_name=head + node.name, ) root.add_child(item) - newhead = head + node.name + "::" + newhead = f"{head + node.name}::" render(item, node.children, newhead) tree = self.col.decks.deck_tree() @@ -906,7 +906,7 @@ class SidebarTreeView(QTreeView): SearchTerm(note=nt["name"]), SearchTerm(template=c) ), item_type=SidebarItemType.NOTETYPE_TEMPLATE, - full_name=nt["name"] + "::" + tmpl["name"], + full_name=f"{nt['name']}::{tmpl['name']}", ) item.add_child(child) diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index 6fc1281a5..c4532754a 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -240,7 +240,7 @@ def _packagedCmd(cmd: List[str]) -> Tuple[Any, Dict[str, str]]: del env["LD_LIBRARY_PATH"] if isMac: dir = os.path.dirname(os.path.abspath(__file__)) - exeDir = os.path.abspath(dir + "/../../Resources/audio") + exeDir = os.path.abspath(f"{dir}/../../Resources/audio") else: exeDir = os.path.dirname(os.path.abspath(sys.argv[0])) if isWin and not cmd[0].endswith(".exe"): @@ -353,7 +353,7 @@ class SimpleMpvPlayer(SimpleProcessPlayer, VideoPlayer): def __init__(self, taskman: TaskManager, base_folder: str) -> None: super().__init__(taskman) - self.args += ["--config-dir=" + base_folder] + self.args += [f"--config-dir={base_folder}"] class SimpleMplayerPlayer(SimpleProcessPlayer, SoundOrVideoPlayer): @@ -377,7 +377,7 @@ class MpvManager(MPV, SoundOrVideoPlayer): mpvPath, self.popenEnv = _packagedCmd(["mpv"]) self.executable = mpvPath[0] self._on_done: Optional[OnDoneCallback] = None - self.default_argv += ["--config-dir=" + base_path] + self.default_argv += [f"--config-dir={base_path}"] super().__init__(window_id=None, debug=False) def on_init(self) -> None: @@ -764,7 +764,7 @@ class RecordDialog(QDialog): def _on_timer(self) -> None: self._recorder.on_timer() duration = self._recorder.duration() - self.label.setText(tr(TR.MEDIA_RECORDINGTIME, secs="%0.1f" % duration)) + self.label.setText(tr(TR.MEDIA_RECORDINGTIME, secs=f"{duration:0.1f}")) def accept(self) -> None: self._timer.stop() diff --git a/qt/aqt/stats.py b/qt/aqt/stats.py index 094f293fd..04158ae6c 100644 --- a/qt/aqt/stats.py +++ b/qt/aqt/stats.py @@ -66,7 +66,7 @@ class NewDeckStats(QDialog): def _imagePath(self) -> str: name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time())) - name = "anki-" + tr(TR.STATISTICS_STATS) + name + name = f"anki-{tr(TR.STATISTICS_STATS)}{name}" file = getSaveFile( self, title=tr(TR.STATISTICS_SAVE_PDF), @@ -156,7 +156,7 @@ class DeckStats(QDialog): def _imagePath(self) -> str: name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time())) - name = "anki-" + tr(TR.STATISTICS_STATS) + name + name = f"anki-{tr(TR.STATISTICS_STATS)}{name}" file = getSaveFile( self, title=tr(TR.STATISTICS_SAVE_PDF), @@ -189,7 +189,7 @@ class DeckStats(QDialog): self.report = stats.report(type=self.period) self.form.web.title = "deck stats" self.form.web.stdHtml( - "" + self.report + "", + f"{self.report}", js=["js/vendor/jquery.min.js", "js/vendor/plot.js"], context=self, ) diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py index 4d3327768..d44ff1d26 100644 --- a/qt/aqt/studydeck.py +++ b/qt/aqt/studydeck.py @@ -45,7 +45,7 @@ class StudyDeck(QDialog): self.form.filter.installEventFilter(self) self.cancel = cancel gui_hooks.state_did_reset.append(self.onReset) - self.geomKey = "studyDeck-" + geomKey + self.geomKey = f"studyDeck-{geomKey}" restoreGeom(self, self.geomKey) disable_help_button(self) if not cancel: diff --git a/qt/aqt/tagedit.py b/qt/aqt/tagedit.py index cf2302784..10ac52027 100644 --- a/qt/aqt/tagedit.py +++ b/qt/aqt/tagedit.py @@ -137,4 +137,4 @@ class TagCompleter(QCompleter): self.tags.remove("") except ValueError: pass - return " ".join(self.tags) + " " + return f"{' '.join(self.tags)} " diff --git a/qt/aqt/taglimit.py b/qt/aqt/taglimit.py index e9e182ebf..15c2e9bab 100644 --- a/qt/aqt/taglimit.py +++ b/qt/aqt/taglimit.py @@ -98,12 +98,12 @@ class TagLimit(QDialog): if yes: arr = [] for req in yes: - arr.append('tag:"%s"' % req) - self.tags += "(" + " or ".join(arr) + ")" + arr.append(f'tag:"{req}"') + self.tags += f"({' or '.join(arr)})" if no: arr = [] for req in no: - arr.append('-tag:"%s"' % req) - self.tags += " " + " ".join(arr) + arr.append(f'-tag:"{req}"') + self.tags += f" {' '.join(arr)}" saveGeom(self, "tagLimit") QDialog.accept(self) diff --git a/qt/aqt/tts.py b/qt/aqt/tts.py index 5f5afe74f..2b71a79f5 100644 --- a/qt/aqt/tts.py +++ b/qt/aqt/tts.py @@ -141,7 +141,7 @@ def on_tts_voices( f"{{{{tts {v.lang} voices={v.name}}}}}" # pylint: disable=no-member for v in voices ) - return buf + "" + return f"{buf}" hooks.field_filter.append(on_tts_voices) @@ -199,7 +199,7 @@ class MacTTSPlayer(TTSProcessPlayer): return None original_name = m.group(1).strip() - tidy_name = "Apple_" + original_name.replace(" ", "_") + tidy_name = f"Apple_{original_name.replace(' ', '_')}" return MacVoice(name=tidy_name, original_name=original_name, lang=m.group(2)) diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 7eba24109..8d94aa5c7 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -479,7 +479,7 @@ def getFile( "Ask the user for a file." assert not dir or not key if not dir: - dirkey = key + "Directory" + dirkey = f"{key}Directory" dir = aqt.mw.pm.profile.get(dirkey, "") else: dirkey = None @@ -521,7 +521,7 @@ def getSaveFile( ) -> str: """Ask the user for a file to save. Use DIR_DESCRIPTION as config variable. The file dialog will default to open with FNAME.""" - config_key = dir_description + "Directory" + config_key = f"{dir_description}Directory" defaultPath = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation) base = aqt.mw.pm.profile.get(config_key, defaultPath) @@ -644,8 +644,8 @@ def restore_is_checked(widget: QWidget, key: str) -> None: def save_combo_index_for_session(widget: QComboBox, key: str) -> None: - textKey = key + "ComboActiveText" - indexKey = key + "ComboActiveIndex" + textKey = f"{key}ComboActiveText" + indexKey = f"{key}ComboActiveIndex" aqt.mw.pm.session[textKey] = widget.currentText() aqt.mw.pm.session[indexKey] = widget.currentIndex() @@ -653,8 +653,8 @@ def save_combo_index_for_session(widget: QComboBox, key: str) -> None: def restore_combo_index_for_session( widget: QComboBox, history: List[str], key: str ) -> None: - textKey = key + "ComboActiveText" - indexKey = key + "ComboActiveIndex" + textKey = f"{key}ComboActiveText" + indexKey = f"{key}ComboActiveIndex" text = aqt.mw.pm.session.get(textKey) index = aqt.mw.pm.session.get(indexKey) if text is not None and index is not None: @@ -696,10 +696,10 @@ def mungeQA(col: Collection, txt: str) -> str: def openFolder(path: str) -> None: if isWin: - subprocess.Popen(["explorer", "file://" + path]) + subprocess.Popen(["explorer", f"file://{path}"]) else: with noBundledLibs(): - QDesktopServices.openUrl(QUrl("file://" + path)) + QDesktopServices.openUrl(QUrl(f"file://{path}")) def shortcut(key: str) -> str: @@ -755,13 +755,11 @@ def tooltip( closeTooltip() aw = parent or aqt.mw.app.activeWindow() or aqt.mw lab = CustomLabel( - """\ - + f"""
- + -
%s{msg}
""" - % msg, +""", aw, ) lab.setFrameStyle(QFrame.Panel) @@ -886,9 +884,9 @@ def supportText() -> str: from aqt import mw if isWin: - platname = "Windows " + platform.win32_ver()[0] + platname = f"Windows {platform.win32_ver()[0]}" elif isMac: - platname = "Mac " + platform.mac_ver()[0] + platname = f"Mac {platform.mac_ver()[0]}" else: platname = "Linux" diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index e905e10a1..f5543eaeb 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -97,7 +97,7 @@ class AnkiWebPage(QWebEnginePage): else: level_str = str(level) buf = "JS %(t)s %(f)s:%(a)d %(b)s" % dict( - t=level_str, a=line, f=srcID, b=msg + "\n" + t=level_str, a=line, f=srcID, b=f"{msg}\n" ) if "MathJax localStorage" in buf: # silence localStorage noise @@ -392,10 +392,10 @@ class AnkiWebView(QWebEngineView): family = tr(TR.QT_MISC_SEGOE_UI) button_style = "button { font-family:%s; }" % family button_style += "\n:focus { outline: 1px solid %s; }" % color_hl - font = "font-size:12px;font-family:%s;" % family + font = f"font-size:12px;font-family:{family};" elif isMac: family = "Helvetica" - font = 'font-size:15px;font-family:"%s";' % family + font = f'font-size:15px;font-family:"{family}";' button_style = """ button { -webkit-appearance: none; background: #fff; border: 1px solid #ccc; border-radius:5px; font-family: Helvetica }""" @@ -403,7 +403,7 @@ border-radius:5px; font-family: Helvetica }""" family = self.font().family() color_hl_txt = palette.color(QPalette.HighlightedText).name() color_btn = palette.color(QPalette.Button).name() - font = 'font-size:14px;font-family:"%s";' % family + font = f'font-size:14px;font-family:"{family}";' button_style = """ /* Buttons */ button{ @@ -503,7 +503,7 @@ body {{ zoom: {zoom}; background: {background}; direction: {lang_dir}; {font} }} return f"http://127.0.0.1:{mw.mediaServer.getPort()}{subpath}{path}" def bundledScript(self, fname: str) -> str: - return '' % self.webBundlePath(fname) + return f'' def bundledCSS(self, fname: str) -> str: return '' % self.webBundlePath( @@ -641,5 +641,5 @@ document.head.appendChild(style); else: extra = "" self.hide_while_preserving_layout() - self.load(QUrl(f"{mw.serverURL()}_anki/pages/{name}.html" + extra)) + self.load(QUrl(f"{mw.serverURL()}_anki/pages/{name}.html{extra}")) self.inject_dynamic_style_and_show() diff --git a/qt/aqt/winpaths.py b/qt/aqt/winpaths.py index 6542a6c32..ce5530a35 100644 --- a/qt/aqt/winpaths.py +++ b/qt/aqt/winpaths.py @@ -86,7 +86,7 @@ def _err_unless_zero(result): if result == 0: return result else: - raise WinPathsException("Failed to retrieve windows path: %s" % result) + raise WinPathsException(f"Failed to retrieve windows path: {result}") _SHGetFolderPath = windll.shell32.SHGetFolderPathW