convert qt strings to f-strings with flynt

Also revealed an incorrect type def in editor.py that mypy wasn't
noticing before :-(
This commit is contained in:
Damien Elmes 2021-02-11 10:09:06 +10:00
parent 5ab115c145
commit 88c002f4eb
30 changed files with 169 additions and 181 deletions

View file

@ -232,7 +232,7 @@ def setupLangAndBackend(
else: else:
qt_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath) qt_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
qt_lang = lang.replace("-", "_") qt_lang = lang.replace("-", "_")
if _qtrans.load("qtbase_" + qt_lang, qt_dir): if _qtrans.load(f"qtbase_{qt_lang}", qt_dir):
app.installTranslator(_qtrans) app.installTranslator(_qtrans)
return anki.lang.current_i18n return anki.lang.current_i18n
@ -249,7 +249,7 @@ class AnkiApp(QApplication):
appMsg = pyqtSignal(str) appMsg = pyqtSignal(str)
KEY = "anki" + checksum(getpass.getuser()) KEY = f"anki{checksum(getpass.getuser())}"
TMOUT = 30000 TMOUT = 30000
def __init__(self, argv: List[str]) -> None: 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 # as there's no such profile
if isMac and len(argv) > 1 and argv[1].startswith("-psn"): if isMac and len(argv) > 1 and argv[1].startswith("-psn"):
argv = [argv[0]] 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.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("-b", "--base", help="path to base folder", default="")
parser.add_argument("-p", "--profile", help="profile name to load", default="") parser.add_argument("-p", "--profile", help="profile name to load", default="")
@ -424,7 +424,7 @@ def run() -> None:
QMessageBox.critical( QMessageBox.critical(
None, None,
"Startup Error", "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()}",
) )

View file

@ -81,7 +81,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
(add-on provided name [Add-on folder, installed at, version, is config changed]) (add-on provided name [Add-on folder, installed at, version, is config changed])
{newline.join(sorted(inactive))} {newline.join(sorted(inactive))}
""" """
info = " " + " ".join(info.splitlines(True)) info = f" {' '.join(info.splitlines(True))}"
QApplication.clipboard().setText(info) QApplication.clipboard().setText(info)
tooltip(tr(TR.ABOUT_COPIED_TO_CLIPBOARD), parent=dialog) tooltip(tr(TR.ABOUT_COPIED_TO_CLIPBOARD), parent=dialog)
@ -93,9 +93,9 @@ def show(mw: aqt.AnkiQt) -> QDialog:
# WebView contents # WebView contents
###################################################################### ######################################################################
abouttext = "<center><img src='/_anki/imgs/anki-logo-thin.png'></center>" abouttext = "<center><img src='/_anki/imgs/anki-logo-thin.png'></center>"
abouttext += "<p>" + tr(TR.ABOUT_ANKI_IS_A_FRIENDLY_INTELLIGENT_SPACED) abouttext += f"<p>{tr(TR.ABOUT_ANKI_IS_A_FRIENDLY_INTELLIGENT_SPACED)}"
abouttext += "<p>" + tr(TR.ABOUT_ANKI_IS_LICENSED_UNDER_THE_AGPL3) abouttext += f"<p>{tr(TR.ABOUT_ANKI_IS_LICENSED_UNDER_THE_AGPL3)}"
abouttext += "<p>" + tr(TR.ABOUT_VERSION, val=versionWithBuild()) + "<br>" abouttext += f"<p>{tr(TR.ABOUT_VERSION, val=versionWithBuild())}<br>"
abouttext += ("Python %s Qt %s PyQt %s<br>") % ( abouttext += ("Python %s Qt %s PyQt %s<br>") % (
platform.python_version(), platform.python_version(),
QT_VERSION_STR, QT_VERSION_STR,
@ -211,8 +211,8 @@ def show(mw: aqt.AnkiQt) -> QDialog:
abouttext += "<p>" + tr( abouttext += "<p>" + tr(
TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES, cont=", ".join(allusers) TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES, cont=", ".join(allusers)
) )
abouttext += "<p>" + tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE) abouttext += f"<p>{tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE)}"
abouttext += "<p>" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE) abouttext += f"<p>{tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE)}"
abt.label.setMinimumWidth(800) abt.label.setMinimumWidth(800)
abt.label.setMinimumHeight(600) abt.label.setMinimumHeight(600)
dialog.show() dialog.show()

View file

@ -84,7 +84,7 @@ class AddCards(QDialog):
self.helpButton.setAutoDefault(False) self.helpButton.setAutoDefault(False)
bb.addButton(self.helpButton, QDialogButtonBox.HelpRole) bb.addButton(self.helpButton, QDialogButtonBox.HelpRole)
# history # history
b = bb.addButton(tr(TR.ADDING_HISTORY) + " " + downArrow(), ar) b = bb.addButton(f"{tr(TR.ADDING_HISTORY)} {downArrow()}", ar)
if isMac: if isMac:
sc = "Ctrl+Shift+H" sc = "Ctrl+Shift+H"
else: else:
@ -149,7 +149,7 @@ class AddCards(QDialog):
fields = note.fields fields = note.fields
txt = htmlToTextLine(", ".join(fields)) txt = htmlToTextLine(", ".join(fields))
if len(txt) > 30: if len(txt) > 30:
txt = txt[:30] + "..." txt = f"{txt[:30]}..."
line = tr(TR.ADDING_EDIT, val=txt) line = tr(TR.ADDING_EDIT, val=txt)
line = gui_hooks.addcards_will_add_history_entry(line, note) line = gui_hooks.addcards_will_add_history_entry(line, note)
a = m.addAction(line) a = m.addAction(line)

View file

@ -309,7 +309,7 @@ class AddonManager:
meta = self.addon_meta(dir) meta = self.addon_meta(dir)
name = meta.human_name() name = meta.human_name()
if not meta.enabled: if not meta.enabled:
name += " " + tr(TR.ADDONS_DISABLED) name += f" {tr(TR.ADDONS_DISABLED)}"
return name return name
# Conflict resolution # Conflict resolution
@ -741,11 +741,9 @@ class AddonsDialog(QDialog):
name = addon.human_name() name = addon.human_name()
if not addon.enabled: if not addon.enabled:
return name + " " + tr(TR.ADDONS_DISABLED2) return f"{name} {tr(TR.ADDONS_DISABLED2)}"
elif not addon.compatible(): elif not addon.compatible():
return ( return f"{name} {tr(TR.ADDONS_REQUIRES, val=self.compatible_string(addon))}"
name + " " + tr(TR.ADDONS_REQUIRES, val=self.compatible_string(addon))
)
return name return name
@ -818,7 +816,7 @@ class AddonsDialog(QDialog):
if not addon: if not addon:
return return
if re.match(r"^\d+$", addon): if re.match(r"^\d+$", addon):
openLink(aqt.appShared + f"info/{addon}") openLink(f"{aqt.appShared}info/{addon}")
else: else:
showWarning(tr(TR.ADDONS_ADDON_WAS_NOT_DOWNLOADED_FROM_ANKIWEB)) 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]: def onInstallFiles(self, paths: Optional[List[str]] = None) -> Optional[bool]:
if not paths: 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( paths_ = getFile(
self, tr(TR.ADDONS_INSTALL_ADDONS), None, key, key="addons", multi=True self, tr(TR.ADDONS_INSTALL_ADDONS), None, key, key="addons", multi=True
) )
@ -924,7 +922,7 @@ class GetAddons(QDialog):
saveGeom(self, "getaddons") saveGeom(self, "getaddons")
def onBrowse(self) -> None: def onBrowse(self) -> None:
openLink(aqt.appShared + "addons/2.1") openLink(f"{aqt.appShared}addons/2.1")
def accept(self) -> None: def accept(self) -> None:
# get codes # get codes
@ -946,7 +944,7 @@ def download_addon(client: HttpClient, id: int) -> Union[DownloadOk, DownloadErr
"Fetch a single add-on from AnkiWeb." "Fetch a single add-on from AnkiWeb."
try: try:
resp = client.get( 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: if resp.status_code != 200:
return DownloadError(status_code=resp.status_code) 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) text = tr(TR.ADDONS_ONE_OR_MORE_ERRORS_OCCURRED)
else: else:
text = tr(TR.ADDONS_DOWNLOAD_COMPLETE_PLEASE_RESTART_ANKI_TO) text = tr(TR.ADDONS_DOWNLOAD_COMPLETE_PLEASE_RESTART_ANKI_TO)
text += "<br><br>" + download_log_to_html(log) text += f"<br><br>{download_log_to_html(log)}"
if have_problem: if have_problem:
showWarning(text, textFormat="rich", parent=parent) showWarning(text, textFormat="rich", parent=parent)
@ -1153,7 +1151,7 @@ def _fetch_update_info_batch(
"""Get update info from AnkiWeb. """Get update info from AnkiWeb.
Chunk must not contain more than 25 ids.""" 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: if resp.status_code == 200:
return resp.json() return resp.json()
else: else:
@ -1366,7 +1364,7 @@ class ConfigEditor(QDialog):
showInfo(msg) showInfo(msg)
return return
except Exception as e: except Exception as e:
showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + " " + repr(e)) showInfo(f"{tr(TR.ADDONS_INVALID_CONFIGURATION)} {repr(e)}")
return return
if not isinstance(new_conf, dict): if not isinstance(new_conf, dict):
@ -1419,7 +1417,7 @@ def installAddonPackages(
if log: if log:
log_html = "<br>".join(log) log_html = "<br>".join(log)
if advise_restart: if advise_restart:
log_html += "<br><br>" + tr(TR.ADDONS_PLEASE_RESTART_ANKI_TO_COMPLETE_THE) log_html += f"<br><br>{tr(TR.ADDONS_PLEASE_RESTART_ANKI_TO_COMPLETE_THE)}"
if len(log) == 1 and not strictly_modal: if len(log) == 1 and not strictly_modal:
tooltip(log_html, parent=parent) tooltip(log_html, parent=parent)
else: else:

View file

@ -115,7 +115,7 @@ class CardLayout(QDialog):
self.topAreaForm = aqt.forms.clayout_top.Ui_Form() self.topAreaForm = aqt.forms.clayout_top.Ui_Form()
self.topAreaForm.setupUi(self.topArea) self.topAreaForm.setupUi(self.topArea)
self.topAreaForm.templateOptions.setText( self.topAreaForm.templateOptions.setText(
tr(TR.ACTIONS_OPTIONS) + " " + downArrow() f"{tr(TR.ACTIONS_OPTIONS)} {downArrow()}"
) )
qconnect(self.topAreaForm.templateOptions.clicked, self.onMore) qconnect(self.topAreaForm.templateOptions.clicked, self.onMore)
qconnect( qconnect(
@ -305,7 +305,7 @@ class CardLayout(QDialog):
qconnect(pform.preview_front.clicked, self.on_preview_toggled) qconnect(pform.preview_front.clicked, self.on_preview_toggled)
qconnect(pform.preview_back.clicked, self.on_preview_toggled) qconnect(pform.preview_back.clicked, self.on_preview_toggled)
pform.preview_settings.setText( 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) qconnect(pform.preview_settings.clicked, self.on_preview_settings)
@ -491,7 +491,7 @@ class CardLayout(QDialog):
text = a text = a
# use _showAnswer to avoid the longer delay # 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( self.preview_web.eval(
f"_emulateMobile({json.dumps(self.mobile_emulation_enabled)});" f"_emulateMobile({json.dumps(self.mobile_emulation_enabled)});"
) )
@ -522,14 +522,14 @@ class CardLayout(QDialog):
def answerRepl(match: Match) -> str: def answerRepl(match: Match) -> str:
res = self.mw.reviewer.correct("exomple", "an example") res = self.mw.reviewer.correct("exomple", "an example")
if hadHR: if hadHR:
res = "<hr id=answer>" + res res = f"<hr id=answer>{res}"
return res return res
repl: Union[str, Callable] repl: Union[str, Callable]
if type == "q": if type == "q":
repl = "<input id='typeans' type=text value='exomple' readonly='readonly'>" repl = "<input id='typeans' type=text value='exomple' readonly='readonly'>"
repl = "<center>%s</center>" % repl repl = f"<center>{repl}</center>"
else: else:
repl = answerRepl repl = answerRepl
return re.sub(r"\[\[type:.+?\]\]", repl, txt) return re.sub(r"\[\[type:.+?\]\]", repl, txt)

View file

@ -59,7 +59,7 @@ class CustomStudy(QDialog):
def plus(num: Union[int, str]) -> str: def plus(num: Union[int, str]) -> str:
if num == 1000: if num == 1000:
num = "1000+" num = "1000+"
return "<b>" + str(num) + "</b>" return f"<b>{str(num)}</b>"
if idx == RADIO_NEW: if idx == RADIO_NEW:
new = self.mw.col.sched.totalNewForCurrentDeck() new = self.mw.col.sched.totalNewForCurrentDeck()

View file

@ -288,7 +288,7 @@ class DeckBrowser:
extra = tr(TR.DECKS_IT_HAS_CARD, count=count) extra = tr(TR.DECKS_IT_HAS_CARD, count=count)
if askUser( 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 True
return False return False
@ -332,4 +332,4 @@ class DeckBrowser:
) )
def _onShared(self) -> None: def _onShared(self) -> None:
openLink(aqt.appShared + "decks/") openLink(f"{aqt.appShared}decks/")

View file

@ -130,7 +130,7 @@ class Editor:
None, None,
"fields", "fields",
tr(TR.EDITING_CUSTOMIZE_FIELDS), tr(TR.EDITING_CUSTOMIZE_FIELDS),
tr(TR.EDITING_FIELDS) + "...", f"{tr(TR.EDITING_FIELDS)}...",
disables=False, disables=False,
rightside=False, rightside=False,
), ),
@ -138,7 +138,7 @@ class Editor:
None, None,
"cards", "cards",
tr(TR.EDITING_CUSTOMIZE_CARD_TEMPLATES_CTRLANDL), tr(TR.EDITING_CUSTOMIZE_CARD_TEMPLATES_CTRLANDL),
tr(TR.EDITING_CARDS) + "...", f"{tr(TR.EDITING_CARDS)}...",
disables=False, disables=False,
rightside=False, rightside=False,
), ),
@ -239,7 +239,7 @@ class Editor:
with open(path, "rb") as fp: with open(path, "rb") as fp:
data = fp.read() data = fp.read()
data64 = b"".join(base64.encodebytes(data).splitlines()) 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( def addButton(
self, self,
@ -314,7 +314,7 @@ class Editor:
else: else:
imgelm = "" imgelm = ""
if label or not imgelm: if label or not imgelm:
labelelm = """<span class=blabel>{}</span>""".format(label or cmd) labelelm = f"""<span class=blabel>{label or cmd}</span>"""
else: else:
labelelm = "" labelelm = ""
if id: if id:
@ -539,7 +539,7 @@ class Editor:
if err == 2: if err == 2:
cols[0] = "dupe" cols[0] = "dupe"
self.web.eval("setBackgrounds(%s);" % json.dumps(cols)) self.web.eval(f"setBackgrounds({json.dumps(cols)});")
def showDupes(self) -> None: def showDupes(self) -> None:
aqt.dialogs.open( aqt.dialogs.open(
@ -734,23 +734,23 @@ class Editor:
self._wrapWithColour(self.fcolour) self._wrapWithColour(self.fcolour)
def _updateForegroundButton(self) -> None: def _updateForegroundButton(self) -> None:
self.web.eval("setFGButton('%s')" % self.fcolour) self.web.eval(f"setFGButton('{self.fcolour}')")
def onColourChanged(self) -> None: def onColourChanged(self) -> None:
self._updateForegroundButton() self._updateForegroundButton()
self.mw.pm.profile["lastColour"] = self.fcolour self.mw.pm.profile["lastColour"] = self.fcolour
def _wrapWithColour(self, colour: str) -> None: def _wrapWithColour(self, colour: str) -> None:
self.web.eval("setFormat('forecolor', '%s')" % colour) self.web.eval(f"setFormat('forecolor', '{colour}')")
# Audio/video/images # Audio/video/images
###################################################################### ######################################################################
def onAddMedia(self) -> None: def onAddMedia(self) -> None:
extension_filter = " ".join( 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: def accept(file: str) -> None:
self.addMedia(file, canDelete=True) self.addMedia(file, canDelete=True)
@ -770,7 +770,7 @@ class Editor:
except Exception as e: except Exception as e:
showWarning(str(e)) showWarning(str(e))
return 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: def _addMedia(self, path: str, canDelete: bool = False) -> str:
"Add to media folder and return local img or sound tag." "Add to media folder and return local img or sound tag."
@ -810,15 +810,15 @@ class Editor:
ext = fname.split(".")[-1].lower() ext = fname.split(".")[-1].lower()
if ext in pics: if ext in pics:
name = urllib.parse.quote(fname.encode("utf8")) name = urllib.parse.quote(fname.encode("utf8"))
return '<img src="%s">' % name return f'<img src="{name}">'
else: else:
av_player.play_file(fname) 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]: def urlToFile(self, url: str) -> Optional[str]:
l = url.lower() l = url.lower()
for suffix in pics + audio: for suffix in pics + audio:
if l.endswith("." + suffix): if l.endswith(f".{suffix}"):
return self._retrieveURL(url) return self._retrieveURL(url)
# not a supported type # not a supported type
return None return None
@ -842,7 +842,7 @@ class Editor:
data = base64.b64decode(b64data, validate=True) data = base64.b64decode(b64data, validate=True)
if ext == "jpeg": if ext == "jpeg":
ext = "jpg" ext = "jpg"
return self._addPastedImage(data, "." + ext) return self._addPastedImage(data, f".{ext}")
return "" return ""
@ -857,7 +857,7 @@ class Editor:
def _addPastedImage(self, data: bytes, ext: str) -> str: def _addPastedImage(self, data: bytes, ext: str) -> str:
# hash and write # hash and write
csum = checksum(data) csum = checksum(data)
fname = "{}-{}{}".format("paste", csum, ext) fname = f"paste-{csum}{ext}"
return self._addMediaFromData(fname, data) return self._addMediaFromData(fname, data)
def _retrieveURL(self, url: str) -> Optional[str]: def _retrieveURL(self, url: str) -> Optional[str]:
@ -967,9 +967,7 @@ class Editor:
ext = "true" ext = "true"
else: else:
ext = "false" ext = "false"
self.web.eval( self.web.eval(f"pasteHTML({json.dumps(html)}, {json.dumps(internal)}, {ext});")
"pasteHTML(%s, %s, %s);" % (json.dumps(html), json.dumps(internal), ext)
)
def doDrop(self, html: str, internal: bool, extended: bool = False) -> None: def doDrop(self, html: str, internal: bool, extended: bool = False) -> None:
def pasteIfField(ret: bool) -> None: def pasteIfField(ret: bool) -> None:
@ -1187,8 +1185,8 @@ class EditorWebView(AnkiWebView):
token = html.escape(token).replace("\t", " " * 4) token = html.escape(token).replace("\t", " " * 4)
# if there's more than one consecutive space, # if there's more than one consecutive space,
# use non-breaking spaces for the second one on # use non-breaking spaces for the second one on
def repl(match: Match) -> None: def repl(match: Match) -> str:
return match.group(1).replace(" ", "&nbsp;") + " " return f"{match.group(1).replace(' ', '&nbsp;')} "
token = re.sub(" ( +)", repl, token) token = re.sub(" ( +)", repl, token)
processed.append(token) processed.append(token)
@ -1247,7 +1245,7 @@ class EditorWebView(AnkiWebView):
if not mime.hasHtml(): if not mime.hasHtml():
return return
html = mime.html() html = mime.html()
mime.setHtml("<!--anki-->" + html) mime.setHtml(f"<!--anki-->{html}")
clip.setMimeData(mime) clip.setMimeData(mime)
def contextMenuEvent(self, evt: QContextMenuEvent) -> None: def contextMenuEvent(self, evt: QContextMenuEvent) -> None:

View file

@ -95,13 +95,13 @@ class ErrorHandler(QObject):
if self.mw.addonManager.dirty: if self.mw.addonManager.dirty:
txt = markdown(tr(TR.ERRORS_ADDONS_ACTIVE_POPUP)) txt = markdown(tr(TR.ERRORS_ADDONS_ACTIVE_POPUP))
error = supportText() + self._addonText(error) + "\n" + error error = f"{supportText() + self._addonText(error)}\n{error}"
else: else:
txt = markdown(tr(TR.ERRORS_STANDARD_POPUP)) txt = markdown(tr(TR.ERRORS_STANDARD_POPUP))
error = supportText() + "\n" + error error = f"{supportText()}\n{error}"
# show dialog # show dialog
txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" txt = f"{txt}<div style='white-space: pre-wrap'>{error}</div>"
showText(txt, type="html", copyBtn=True) showText(txt, type="html", copyBtn=True)
def _addonText(self, error: str) -> str: def _addonText(self, error: str) -> str:
@ -113,6 +113,6 @@ class ErrorHandler(QObject):
mw.addonManager.addonName(i) for i in dict.fromkeys(reversed(matches)) mw.addonManager.addonName(i) for i in dict.fromkeys(reversed(matches))
] ]
# highlight importance of first add-on: # highlight importance of first add-on:
addons[0] = "<b>{}</b>".format(addons[0]) addons[0] = f"<b>{addons[0]}</b>"
addons_str = ", ".join(addons) 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"

View file

@ -60,7 +60,7 @@ class FieldDialog(QDialog):
self.currentIdx = None self.currentIdx = None
self.form.fieldList.clear() self.form.fieldList.clear()
for c, f in enumerate(self.model["flds"]): 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: def setupSignals(self) -> None:
f = self.form f = self.form
@ -244,7 +244,7 @@ class FieldDialog(QDialog):
fut.result() fut.result()
except TemplateError as e: except TemplateError as e:
# fixme: i18n # fixme: i18n
showWarning("Unable to save changes: " + str(e)) showWarning(f"Unable to save changes: {str(e)}")
return return
self.mw.reset() self.mw.reset()
tooltip("Changes saved.", parent=self.mw) tooltip("Changes saved.", parent=self.mw)

View file

@ -194,7 +194,7 @@ class ImportDialog(QDialog):
showUnicodeWarning() showUnicodeWarning()
return return
except Exception as e: 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)) err = repr(str(e))
if "1-character string" in err: if "1-character string" in err:
msg += err msg += err
@ -205,7 +205,7 @@ class ImportDialog(QDialog):
showText(msg) showText(msg)
return return
else: else:
txt = tr(TR.IMPORTING_IMPORTING_COMPLETE) + "\n" txt = f"{tr(TR.IMPORTING_IMPORTING_COMPLETE)}\n"
if self.importer.log: if self.importer.log:
txt += "\n".join(self.importer.log) txt += "\n".join(self.importer.log)
self.close() self.close()
@ -326,7 +326,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
if done: if done:
break break
for mext in re.findall(r"[( ]?\*\.(.+?)[) ]", i[0]): for mext in re.findall(r"[( ]?\*\.(.+?)[) ]", i[0]):
if file.endswith("." + mext): if file.endswith(f".{mext}"):
importerClass = i[1] importerClass = i[1]
done = True done = True
break break
@ -352,7 +352,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
if msg == "'unknownFormat'": if msg == "'unknownFormat'":
showWarning(tr(TR.IMPORTING_UNKNOWN_FILE_FORMAT)) showWarning(tr(TR.IMPORTING_UNKNOWN_FILE_FORMAT))
else: else:
msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n" msg = f"{tr(TR.IMPORTING_FAILED_DEBUG_INFO)}\n"
msg += str(traceback.format_exc()) msg += str(traceback.format_exc())
showText(msg) showText(msg)
return return
@ -392,7 +392,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
elif "readonly" in err: elif "readonly" in err:
showWarning(tr(TR.IMPORTING_UNABLE_TO_IMPORT_FROM_A_READONLY)) showWarning(tr(TR.IMPORTING_UNABLE_TO_IMPORT_FROM_A_READONLY))
else: else:
msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n" msg = f"{tr(TR.IMPORTING_FAILED_DEBUG_INFO)}\n"
msg += str(traceback.format_exc()) msg += str(traceback.format_exc())
showText(msg) showText(msg)
else: else:

View file

@ -397,7 +397,7 @@ class AnkiQt(QMainWindow):
restoreGeom(self, "mainWindow") restoreGeom(self, "mainWindow")
restoreState(self, "mainWindow") restoreState(self, "mainWindow")
# titlebar # titlebar
self.setWindowTitle(self.pm.name + " - Anki") self.setWindowTitle(f"{self.pm.name} - Anki")
# show and raise window for osx # show and raise window for osx
self.show() self.show()
self.activateWindow() self.activateWindow()
@ -489,7 +489,7 @@ class AnkiQt(QMainWindow):
) )
else: else:
showWarning( 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 # clean up open collection if possible
try: try:
@ -643,14 +643,14 @@ class AnkiQt(QMainWindow):
def moveToState(self, state: str, *args: Any) -> None: def moveToState(self, state: str, *args: Any) -> None:
# print("-> move from", self.state, "to", state) # print("-> move from", self.state, "to", state)
oldState = self.state or "dummy" oldState = self.state or "dummy"
cleanup = getattr(self, "_" + oldState + "Cleanup", None) cleanup = getattr(self, f"_{oldState}Cleanup", None)
if cleanup: if cleanup:
# pylint: disable=not-callable # pylint: disable=not-callable
cleanup(state) cleanup(state)
self.clearStateShortcuts() self.clearStateShortcuts()
self.state = state self.state = state
gui_hooks.state_will_change(state, oldState) gui_hooks.state_will_change(state, oldState)
getattr(self, "_" + state + "State")(oldState, *args) getattr(self, f"_{state}State")(oldState, *args)
if state != "resetRequired": if state != "resetRequired":
self.bottomWeb.show() self.bottomWeb.show()
gui_hooks.state_did_change(state, oldState) gui_hooks.state_did_change(state, oldState)
@ -730,14 +730,13 @@ class AnkiQt(QMainWindow):
i = tr(TR.QT_MISC_WAITING_FOR_EDITING_TO_FINISH) i = tr(TR.QT_MISC_WAITING_FOR_EDITING_TO_FINISH)
b = self.button("refresh", tr(TR.QT_MISC_RESUME_NOW), id="resume") b = self.button("refresh", tr(TR.QT_MISC_RESUME_NOW), id="resume")
self.web.stdHtml( self.web.stdHtml(
""" f"""
<center><div style="height: 100%%"> <center><div style="height: 100%">
<div style="position:relative; vertical-align: middle;"> <div style="position:relative; vertical-align: middle;">
%s<br><br> {i}<br><br>
%s</div></div></center> {b}</div></div></center>
<script>$('#resume').focus()</script> <script>$('#resume').focus()</script>
""" """,
% (i, b),
context=web_context, context=web_context,
) )
self.bottomWeb.hide() self.bottomWeb.hide()
@ -755,7 +754,7 @@ class AnkiQt(QMainWindow):
id: str = "", id: str = "",
extra: str = "", extra: str = "",
) -> str: ) -> str:
class_ = "but " + class_ class_ = f"but {class_}"
if key: if key:
key = tr(TR.ACTIONS_SHORTCUT_KEY, val=key) key = tr(TR.ACTIONS_SHORTCUT_KEY, val=key)
else: else:
@ -989,7 +988,7 @@ title="%s" %s>%s</button>""" % (
def setStateShortcuts(self, shortcuts: List[Tuple[str, Callable]]) -> None: def setStateShortcuts(self, shortcuts: List[Tuple[str, Callable]]) -> None:
gui_hooks.state_shortcuts_will_change(self.state, shortcuts) gui_hooks.state_shortcuts_will_change(self.state, shortcuts)
# legacy hook # legacy hook
runHook(self.state + "StateShortcuts", shortcuts) runHook(f"{self.state}StateShortcuts", shortcuts)
self.stateShortcuts = self.applyShortcuts(shortcuts) self.stateShortcuts = self.applyShortcuts(shortcuts)
def clearStateShortcuts(self) -> None: def clearStateShortcuts(self) -> None:
@ -1285,7 +1284,7 @@ title="%s" %s>%s</button>""" % (
if not existed: if not existed:
f.write(b"nid\tmid\tfields\n") f.write(b"nid\tmid\tfields\n")
for id, mid, flds in col.db.execute( 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) fields = splitFields(flds)
f.write(("\t".join([str(id), str(mid)] + fields)).encode("utf8")) f.write(("\t".join([str(id), str(mid)] + fields)).encode("utf8"))
@ -1471,9 +1470,9 @@ title="%s" %s>%s</button>""" % (
buf = "" buf = ""
for c, line in enumerate(text.strip().split("\n")): for c, line in enumerate(text.strip().split("\n")):
if c == 0: if c == 0:
buf += ">>> %s\n" % line buf += f">>> {line}\n"
else: else:
buf += "... %s\n" % line buf += f"... {line}\n"
try: try:
to_append = buf + (self._output or "<no output>") to_append = buf + (self._output or "<no output>")
to_append = gui_hooks.debug_console_did_evaluate_python( to_append = gui_hooks.debug_console_did_evaluate_python(
@ -1616,7 +1615,7 @@ title="%s" %s>%s</button>""" % (
self.mediaServer.start() self.mediaServer.start()
def baseHTML(self) -> str: def baseHTML(self) -> str:
return '<base href="%s">' % self.serverURL() return f'<base href="{self.serverURL()}">'
def serverURL(self) -> str: def serverURL(self) -> str:
return "http://127.0.0.1:%d/" % self.mediaServer.getPort() return "http://127.0.0.1:%d/" % self.mediaServer.getPort()

View file

@ -33,7 +33,7 @@ def _getExportFolder() -> str:
return webInSrcFolder return webInSrcFolder
elif isMac: elif isMac:
dir = os.path.dirname(os.path.abspath(__file__)) dir = os.path.dirname(os.path.abspath(__file__))
return os.path.abspath(dir + "/../../Resources/web") return os.path.abspath(f"{dir}/../../Resources/web")
else: else:
if os.environ.get("TEST_TARGET"): if os.environ.get("TEST_TARGET"):
# running tests in bazel; we have no data # 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)) isdir = os.path.isdir(os.path.join(directory, path))
except ValueError: except ValueError:
return flask.make_response( return flask.make_response(
"Path for '%s - %s' is too long!" % (directory, path), f"Path for '{directory} - {path}' is too long!",
HTTPStatus.BAD_REQUEST, 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 # protect against directory transversal: https://security.openstack.org/guidelines/dg_using-file-paths.html
if not fullpath.startswith(directory): if not fullpath.startswith(directory):
return flask.make_response( 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, HTTPStatus.FORBIDDEN,
) )
if isdir: if isdir:
return flask.make_response( 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, HTTPStatus.FORBIDDEN,
) )
@ -221,7 +221,7 @@ def _redirectWebExports(path: str) -> Tuple[str, str]:
addMgr = aqt.mw.addonManager addMgr = aqt.mw.addonManager
except AttributeError as error: except AttributeError as error:
if devMode: if devMode:
print("_redirectWebExports: %s" % error) print(f"_redirectWebExports: {error}")
return None return None
try: try:

View file

@ -128,7 +128,7 @@ class Models(QDialog):
self.models = notetypes self.models = notetypes
for m in self.models: for m in self.models:
mUse = tr(TR.BROWSING_NOTE_COUNT, count=m.use_count) 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.addItem(item)
self.form.modelsList.setCurrentRow(row) self.form.modelsList.setCurrentRow(row)

View file

@ -119,9 +119,9 @@ class MPVBase:
"""Prepare the argument list for the mpv process.""" """Prepare the argument list for the mpv process."""
self.argv = [self.executable] self.argv = [self.executable]
self.argv += self.default_argv 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: 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): def _start_process(self):
"""Start the mpv process.""" """Start the mpv process."""
@ -258,7 +258,7 @@ class MPVBase:
buf = buf[newline + 1 :] buf = buf[newline + 1 :]
if self.debug: if self.debug:
sys.stdout.write("<<< " + data.decode("utf8", "replace")) sys.stdout.write(f"<<< {data.decode('utf8', 'replace')}")
message = self._parse_message(data) message = self._parse_message(data)
self._handle_message(message) self._handle_message(message)
@ -298,7 +298,7 @@ class MPVBase:
self._event_queue.put(message) self._event_queue.put(message)
else: else:
raise MPVCommunicationError("invalid message %r" % message) raise MPVCommunicationError(f"invalid message {message!r}")
def _send_message(self, message, timeout=None): def _send_message(self, message, timeout=None):
"""Send a message/command to the mpv process, message must be a """Send a message/command to the mpv process, message must be a
@ -308,7 +308,7 @@ class MPVBase:
data = self._compose_message(message) data = self._compose_message(message)
if self.debug: 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 # Request/response cycles are coordinated across different threads, so
# that they don't get mixed up. This makes it possible to use commands # 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) self._send_message(message, timeout)
return self._get_response(timeout) return self._get_response(timeout)
except MPVCommandError as e: except MPVCommandError as e:
raise MPVCommandError("%r: %s" % (message["command"], e)) raise MPVCommandError(f"{message['command']!r}: {e}")
except Exception as e: except Exception as e:
if _retry: if _retry:
print("mpv timed out, restarting") print("mpv timed out, restarting")
@ -512,7 +512,7 @@ class MPV(MPVBase):
return return
if message["event"] == "property-change": if message["event"] == "property-change":
name = "property-" + message["name"] name = f"property-{message['name']}"
else: else:
name = message["event"] name = message["event"]
@ -527,7 +527,7 @@ class MPV(MPVBase):
try: try:
self.command("enable_event", name) self.command("enable_event", name)
except MPVCommandError: except MPVCommandError:
raise MPVError("no such event %r" % name) raise MPVError(f"no such event {name!r}")
self._callbacks.setdefault(name, []).append(callback) self._callbacks.setdefault(name, []).append(callback)
@ -538,12 +538,12 @@ class MPV(MPVBase):
try: try:
callbacks = self._callbacks[name] callbacks = self._callbacks[name]
except KeyError: except KeyError:
raise MPVError("no callbacks registered for event %r" % name) raise MPVError(f"no callbacks registered for event {name!r}")
try: try:
callbacks.remove(callback) callbacks.remove(callback)
except ValueError: 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): def register_property_callback(self, name, callback):
"""Register a function `callback` for the property-change event on """Register a function `callback` for the property-change event on
@ -556,9 +556,9 @@ class MPV(MPVBase):
# Apparently observe_property does not check it :-( # Apparently observe_property does not check it :-(
proplist = self.command("get_property", "property-list") proplist = self.command("get_property", "property-list")
if name not in proplist: 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 # 'observe_property' expects some kind of id which can be used later
# for unregistering with 'unobserve_property'. # for unregistering with 'unobserve_property'.
@ -572,15 +572,15 @@ class MPV(MPVBase):
property-change event on property `name`. property-change event on property `name`.
""" """
try: try:
callbacks = self._callbacks["property-" + name] callbacks = self._callbacks[f"property-{name}"]
except KeyError: except KeyError:
raise MPVError("no callbacks registered for property %r" % name) raise MPVError(f"no callbacks registered for property {name!r}")
try: try:
callbacks.remove(callback) callbacks.remove(callback)
except ValueError: except ValueError:
raise MPVError( 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)) serial = self._property_serials.pop((name, callback))

View file

@ -80,7 +80,7 @@ class Overview:
elif url == "decks": elif url == "decks":
self.mw.moveToState("deckBrowser") self.mw.moveToState("deckBrowser")
elif url == "review": 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": elif url == "studymore" or url == "customStudy":
self.onStudyMore() self.onStudyMore()
elif url == "unbury": elif url == "unbury":
@ -180,8 +180,8 @@ class Overview:
def _desc(self, deck: Dict[str, Any]) -> str: def _desc(self, deck: Dict[str, Any]) -> str:
if deck["dyn"]: if deck["dyn"]:
desc = tr(TR.STUDYING_THIS_IS_A_SPECIAL_DECK_FOR) desc = tr(TR.STUDYING_THIS_IS_A_SPECIAL_DECK_FOR)
desc += " " + tr(TR.STUDYING_CARDS_WILL_BE_AUTOMATICALLY_RETURNED_TO) desc += f" {tr(TR.STUDYING_CARDS_WILL_BE_AUTOMATICALLY_RETURNED_TO)}"
desc += " " + tr(TR.STUDYING_DELETING_THIS_DECK_FROM_THE_DECK) desc += f" {tr(TR.STUDYING_DELETING_THIS_DECK_FROM_THE_DECK)}"
else: else:
desc = deck.get("desc", "") desc = deck.get("desc", "")
if deck.get("md", False): if deck.get("md", False):
@ -192,7 +192,7 @@ class Overview:
dyn = "dyn" dyn = "dyn"
else: else:
dyn = "" dyn = ""
return '<div class="descfont descmid description %s">%s</div>' % (dyn, desc) return f'<div class="descfont descmid description {dyn}">{desc}</div>'
def _table(self) -> Optional[str]: def _table(self) -> Optional[str]:
counts = list(self.mw.col.sched.counts()) counts = list(self.mw.col.sched.counts())

View file

@ -214,9 +214,9 @@ class Previewer(QDialog):
av_player.clear_queue_and_maybe_interrupt() av_player.clear_queue_and_maybe_interrupt()
txt = self.mw.prepare_card_text_for_display(txt) 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._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 self._card_changed = False
def _on_show_both_sides(self, toggle: bool) -> None: def _on_show_both_sides(self, toggle: bool) -> None:

View file

@ -522,7 +522,7 @@ create table if not exists profiles
without_unicode_isolation( without_unicode_isolation(
tr( tr(
TR.PROFILES_FOLDER_README, TR.PROFILES_FOLDER_README,
link=appHelpSite + "files?id=startup-options", link=f"{appHelpSite}files?id=startup-options",
) )
) )
) )

View file

@ -45,7 +45,7 @@ class ProgressManager:
def handler() -> None: def handler() -> None:
if requiresCollection and not self.mw.col: if requiresCollection and not self.mw.col:
# no current collection; timer is no longer valid # 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 return
if not self._levels: if not self._levels:

View file

@ -97,9 +97,7 @@ class Reviewer:
mins = int(round(elapsed[0] / 60)) mins = int(round(elapsed[0] / 60))
part2 = tr(TR.STUDYING_MINUTE, count=mins) part2 = tr(TR.STUDYING_MINUTE, count=mins)
fin = tr(TR.STUDYING_FINISH) fin = tr(TR.STUDYING_FINISH)
diag = askUserDialog( diag = askUserDialog(f"{part1} {part2}", [tr(TR.STUDYING_CONTINUE), fin])
"%s %s" % (part1, part2), [tr(TR.STUDYING_CONTINUE), fin]
)
diag.setIcon(QMessageBox.Information) diag.setIcon(QMessageBox.Information)
if diag.run() == fin: if diag.run() == fin:
return self.mw.moveToState("deckBrowser") return self.mw.moveToState("deckBrowser")
@ -142,15 +140,13 @@ class Reviewer:
fade = "" fade = ""
if self.mw.pm.video_driver() == VideoDriver.Software: if self.mw.pm.video_driver() == VideoDriver.Software:
fade = "<script>qFade=0;</script>" fade = "<script>qFade=0;</script>"
return """ return f"""
<div id=_mark>&#x2605;</div> <div id=_mark>&#x2605;</div>
<div id=_flag>&#x2691;</div> <div id=_flag>&#x2691;</div>
{} {fade}
<div id=qa></div> <div id=qa></div>
{} {extra}
""".format( """
fade, extra
)
def _initWeb(self) -> None: def _initWeb(self) -> None:
self._reps = 0 self._reps = 0
@ -207,7 +203,7 @@ class Reviewer:
bodyclass = theme_manager.body_classes_for_card_ord(c.ord) 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._drawFlag()
self._drawMark() self._drawMark()
self._showAnswerButton() self._showAnswerButton()
@ -220,10 +216,10 @@ class Reviewer:
return card.autoplay() return card.autoplay()
def _drawFlag(self) -> None: def _drawFlag(self) -> None:
self.web.eval("_drawFlag(%s);" % self.card.userFlag()) self.web.eval(f"_drawFlag({self.card.userFlag()});")
def _drawMark(self) -> None: 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 # Showing the answer
########################################################################## ##########################################################################
@ -248,7 +244,7 @@ class Reviewer:
a = self._mungeQA(a) a = self._mungeQA(a)
a = gui_hooks.card_will_show(a, c, "reviewAnswer") a = gui_hooks.card_will_show(a, c, "reviewAnswer")
# render and update bottom # render and update bottom
self.web.eval("_showAnswer(%s);" % json.dumps(a)) self.web.eval(f"_showAnswer({json.dumps(a)});")
self._showEaseButtons() self._showEaseButtons()
self.mw.web.setFocus() self.mw.web.setFocus()
# user hook # user hook
@ -399,13 +395,12 @@ class Reviewer:
return re.sub(self.typeAnsPat, "", buf) return re.sub(self.typeAnsPat, "", buf)
return re.sub( return re.sub(
self.typeAnsPat, self.typeAnsPat,
""" f"""
<center> <center>
<input type=text id=typeans onkeypress="_typeAnsPress();" <input type=text id=typeans onkeypress="_typeAnsPress();"
style="font-family: '%s'; font-size: %spx;"> style="font-family: '{self.typeFont}'; font-size: {self.typeSize}px;">
</center> </center>
""" """,
% (self.typeFont, self.typeSize),
buf, buf,
) )
@ -440,7 +435,7 @@ class Reviewer:
if hadHR: if hadHR:
# a hack to ensure the q/a separator falls before the answer # a hack to ensure the q/a separator falls before the answer
# comparison when user is using {{FrontSide}} # comparison when user is using {{FrontSide}}
s = "<hr id=answer>" + s s = f"<hr id=answer>{s}"
return s return s
return re.sub(self.typeAnsPat, repl, buf) return re.sub(self.typeAnsPat, repl, buf)
@ -506,13 +501,13 @@ class Reviewer:
givenElems, correctElems = self.tokenizeComparison(given, correct) givenElems, correctElems = self.tokenizeComparison(given, correct)
def good(s: str) -> str: def good(s: str) -> str:
return "<span class=typeGood>" + html.escape(s) + "</span>" return f"<span class=typeGood>{html.escape(s)}</span>"
def bad(s: str) -> str: def bad(s: str) -> str:
return "<span class=typeBad>" + html.escape(s) + "</span>" return f"<span class=typeBad>{html.escape(s)}</span>"
def missed(s: str) -> str: def missed(s: str) -> str:
return "<span class=typeMissed>" + html.escape(s) + "</span>" return f"<span class=typeMissed>{html.escape(s)}</span>"
if given == correct: if given == correct:
res = good(given) res = good(given)
@ -531,14 +526,14 @@ class Reviewer:
res += good(txt) res += good(txt)
else: else:
res += missed(txt) res += missed(txt)
res = "<div><code id=typeans>" + res + "</code></div>" res = f"<div><code id=typeans>{res}</code></div>"
return res return res
def _noLoneMarks(self, s: str) -> str: def _noLoneMarks(self, s: str) -> str:
# ensure a combining character at the start does not join to # ensure a combining character at the start does not join to
# previous text # previous text
if s and ucd.category(s[0]).startswith("M"): if s and ucd.category(s[0]).startswith("M"):
return "\xa0" + s return f"\xa0{s}"
return s return s
def _getTypedAnswer(self) -> None: def _getTypedAnswer(self) -> None:
@ -602,7 +597,7 @@ time = %(time)d;
def _showEaseButtons(self) -> None: def _showEaseButtons(self) -> None:
middle = self._answerButtons() 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: def _remaining(self) -> str:
if not self.mw.col.conf["dueCounts"]: if not self.mw.col.conf["dueCounts"]:
@ -613,11 +608,11 @@ time = %(time)d;
else: else:
counts = list(self.mw.col.sched.counts(self.card)) counts = list(self.mw.col.sched.counts(self.card))
idx = self.mw.col.sched.countIdx(self.card) idx = self.mw.col.sched.countIdx(self.card)
counts[idx] = "<u>%s</u>" % (counts[idx]) counts[idx] = f"<u>{counts[idx]}</u>"
space = " + " space = " + "
ctxt = "<span class=new-count>%s</span>" % counts[0] ctxt = f"<span class=new-count>{counts[0]}</span>"
ctxt += space + "<span class=learn-count>%s</span>" % counts[1] ctxt += f"{space}<span class=learn-count>{counts[1]}</span>"
ctxt += space + "<span class=review-count>%s</span>" % counts[2] ctxt += f"{space}<span class=review-count>{counts[2]}</span>"
return ctxt return ctxt
def _defaultEase(self) -> int: def _defaultEase(self) -> int:
@ -683,7 +678,7 @@ time = %(time)d;
if not self.mw.col.conf["estTimes"]: if not self.mw.col.conf["estTimes"]:
return "<div class=spacer></div>" return "<div class=spacer></div>"
txt = self.mw.col.sched.nextIvlStr(self.card, i, True) or "&nbsp;" txt = self.mw.col.sched.nextIvlStr(self.card, i, True) or "&nbsp;"
return "<span class=nobold>%s</span><br>" % txt return f"<span class=nobold>{txt}</span><br>"
# Leeches # Leeches
########################################################################## ##########################################################################
@ -692,7 +687,7 @@ time = %(time)d;
# for now # for now
s = tr(TR.STUDYING_CARD_WAS_A_LEECH) s = tr(TR.STUDYING_CARD_WAS_A_LEECH)
if card.queue < 0: if card.queue < 0:
s += " " + tr(TR.STUDYING_IT_HAS_BEEN_SUSPENDED) s += f" {tr(TR.STUDYING_IT_HAS_BEEN_SUSPENDED)}"
tooltip(s) tooltip(s)
# Context menu # Context menu

View file

@ -805,7 +805,7 @@ class SidebarTreeView(QTreeView):
full_name=head + node.name, full_name=head + node.name,
) )
root.add_child(item) root.add_child(item)
newhead = head + node.name + "::" newhead = f"{head + node.name}::"
render(item, node.children, newhead) render(item, node.children, newhead)
tree = self.col.tags.tree() tree = self.col.tags.tree()
@ -854,7 +854,7 @@ class SidebarTreeView(QTreeView):
full_name=head + node.name, full_name=head + node.name,
) )
root.add_child(item) root.add_child(item)
newhead = head + node.name + "::" newhead = f"{head + node.name}::"
render(item, node.children, newhead) render(item, node.children, newhead)
tree = self.col.decks.deck_tree() tree = self.col.decks.deck_tree()
@ -906,7 +906,7 @@ class SidebarTreeView(QTreeView):
SearchTerm(note=nt["name"]), SearchTerm(template=c) SearchTerm(note=nt["name"]), SearchTerm(template=c)
), ),
item_type=SidebarItemType.NOTETYPE_TEMPLATE, item_type=SidebarItemType.NOTETYPE_TEMPLATE,
full_name=nt["name"] + "::" + tmpl["name"], full_name=f"{nt['name']}::{tmpl['name']}",
) )
item.add_child(child) item.add_child(child)

View file

@ -240,7 +240,7 @@ def _packagedCmd(cmd: List[str]) -> Tuple[Any, Dict[str, str]]:
del env["LD_LIBRARY_PATH"] del env["LD_LIBRARY_PATH"]
if isMac: if isMac:
dir = os.path.dirname(os.path.abspath(__file__)) dir = os.path.dirname(os.path.abspath(__file__))
exeDir = os.path.abspath(dir + "/../../Resources/audio") exeDir = os.path.abspath(f"{dir}/../../Resources/audio")
else: else:
exeDir = os.path.dirname(os.path.abspath(sys.argv[0])) exeDir = os.path.dirname(os.path.abspath(sys.argv[0]))
if isWin and not cmd[0].endswith(".exe"): 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: def __init__(self, taskman: TaskManager, base_folder: str) -> None:
super().__init__(taskman) super().__init__(taskman)
self.args += ["--config-dir=" + base_folder] self.args += [f"--config-dir={base_folder}"]
class SimpleMplayerPlayer(SimpleProcessPlayer, SoundOrVideoPlayer): class SimpleMplayerPlayer(SimpleProcessPlayer, SoundOrVideoPlayer):
@ -377,7 +377,7 @@ class MpvManager(MPV, SoundOrVideoPlayer):
mpvPath, self.popenEnv = _packagedCmd(["mpv"]) mpvPath, self.popenEnv = _packagedCmd(["mpv"])
self.executable = mpvPath[0] self.executable = mpvPath[0]
self._on_done: Optional[OnDoneCallback] = None 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) super().__init__(window_id=None, debug=False)
def on_init(self) -> None: def on_init(self) -> None:
@ -764,7 +764,7 @@ class RecordDialog(QDialog):
def _on_timer(self) -> None: def _on_timer(self) -> None:
self._recorder.on_timer() self._recorder.on_timer()
duration = self._recorder.duration() 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: def accept(self) -> None:
self._timer.stop() self._timer.stop()

View file

@ -66,7 +66,7 @@ class NewDeckStats(QDialog):
def _imagePath(self) -> str: def _imagePath(self) -> str:
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time())) 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( file = getSaveFile(
self, self,
title=tr(TR.STATISTICS_SAVE_PDF), title=tr(TR.STATISTICS_SAVE_PDF),
@ -156,7 +156,7 @@ class DeckStats(QDialog):
def _imagePath(self) -> str: def _imagePath(self) -> str:
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time())) 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( file = getSaveFile(
self, self,
title=tr(TR.STATISTICS_SAVE_PDF), title=tr(TR.STATISTICS_SAVE_PDF),
@ -189,7 +189,7 @@ class DeckStats(QDialog):
self.report = stats.report(type=self.period) self.report = stats.report(type=self.period)
self.form.web.title = "deck stats" self.form.web.title = "deck stats"
self.form.web.stdHtml( self.form.web.stdHtml(
"<html><body>" + self.report + "</body></html>", f"<html><body>{self.report}</body></html>",
js=["js/vendor/jquery.min.js", "js/vendor/plot.js"], js=["js/vendor/jquery.min.js", "js/vendor/plot.js"],
context=self, context=self,
) )

View file

@ -45,7 +45,7 @@ class StudyDeck(QDialog):
self.form.filter.installEventFilter(self) self.form.filter.installEventFilter(self)
self.cancel = cancel self.cancel = cancel
gui_hooks.state_did_reset.append(self.onReset) gui_hooks.state_did_reset.append(self.onReset)
self.geomKey = "studyDeck-" + geomKey self.geomKey = f"studyDeck-{geomKey}"
restoreGeom(self, self.geomKey) restoreGeom(self, self.geomKey)
disable_help_button(self) disable_help_button(self)
if not cancel: if not cancel:

View file

@ -137,4 +137,4 @@ class TagCompleter(QCompleter):
self.tags.remove("") self.tags.remove("")
except ValueError: except ValueError:
pass pass
return " ".join(self.tags) + " " return f"{' '.join(self.tags)} "

View file

@ -98,12 +98,12 @@ class TagLimit(QDialog):
if yes: if yes:
arr = [] arr = []
for req in yes: for req in yes:
arr.append('tag:"%s"' % req) arr.append(f'tag:"{req}"')
self.tags += "(" + " or ".join(arr) + ")" self.tags += f"({' or '.join(arr)})"
if no: if no:
arr = [] arr = []
for req in no: for req in no:
arr.append('-tag:"%s"' % req) arr.append(f'-tag:"{req}"')
self.tags += " " + " ".join(arr) self.tags += f" {' '.join(arr)}"
saveGeom(self, "tagLimit") saveGeom(self, "tagLimit")
QDialog.accept(self) QDialog.accept(self)

View file

@ -141,7 +141,7 @@ def on_tts_voices(
f"{{{{tts {v.lang} voices={v.name}}}}}" # pylint: disable=no-member f"{{{{tts {v.lang} voices={v.name}}}}}" # pylint: disable=no-member
for v in voices for v in voices
) )
return buf + "</div>" return f"{buf}</div>"
hooks.field_filter.append(on_tts_voices) hooks.field_filter.append(on_tts_voices)
@ -199,7 +199,7 @@ class MacTTSPlayer(TTSProcessPlayer):
return None return None
original_name = m.group(1).strip() 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)) return MacVoice(name=tidy_name, original_name=original_name, lang=m.group(2))

View file

@ -479,7 +479,7 @@ def getFile(
"Ask the user for a file." "Ask the user for a file."
assert not dir or not key assert not dir or not key
if not dir: if not dir:
dirkey = key + "Directory" dirkey = f"{key}Directory"
dir = aqt.mw.pm.profile.get(dirkey, "") dir = aqt.mw.pm.profile.get(dirkey, "")
else: else:
dirkey = None dirkey = None
@ -521,7 +521,7 @@ def getSaveFile(
) -> str: ) -> str:
"""Ask the user for a file to save. Use DIR_DESCRIPTION as config """Ask the user for a file to save. Use DIR_DESCRIPTION as config
variable. The file dialog will default to open with FNAME.""" 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) defaultPath = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
base = aqt.mw.pm.profile.get(config_key, defaultPath) 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: def save_combo_index_for_session(widget: QComboBox, key: str) -> None:
textKey = key + "ComboActiveText" textKey = f"{key}ComboActiveText"
indexKey = key + "ComboActiveIndex" indexKey = f"{key}ComboActiveIndex"
aqt.mw.pm.session[textKey] = widget.currentText() aqt.mw.pm.session[textKey] = widget.currentText()
aqt.mw.pm.session[indexKey] = widget.currentIndex() 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( def restore_combo_index_for_session(
widget: QComboBox, history: List[str], key: str widget: QComboBox, history: List[str], key: str
) -> None: ) -> None:
textKey = key + "ComboActiveText" textKey = f"{key}ComboActiveText"
indexKey = key + "ComboActiveIndex" indexKey = f"{key}ComboActiveIndex"
text = aqt.mw.pm.session.get(textKey) text = aqt.mw.pm.session.get(textKey)
index = aqt.mw.pm.session.get(indexKey) index = aqt.mw.pm.session.get(indexKey)
if text is not None and index is not None: 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: def openFolder(path: str) -> None:
if isWin: if isWin:
subprocess.Popen(["explorer", "file://" + path]) subprocess.Popen(["explorer", f"file://{path}"])
else: else:
with noBundledLibs(): with noBundledLibs():
QDesktopServices.openUrl(QUrl("file://" + path)) QDesktopServices.openUrl(QUrl(f"file://{path}"))
def shortcut(key: str) -> str: def shortcut(key: str) -> str:
@ -755,13 +755,11 @@ def tooltip(
closeTooltip() closeTooltip()
aw = parent or aqt.mw.app.activeWindow() or aqt.mw aw = parent or aqt.mw.app.activeWindow() or aqt.mw
lab = CustomLabel( lab = CustomLabel(
"""\ f"""<table cellpadding=10>
<table cellpadding=10>
<tr> <tr>
<td>%s</td> <td>{msg}</td>
</tr> </tr>
</table>""" </table>""",
% msg,
aw, aw,
) )
lab.setFrameStyle(QFrame.Panel) lab.setFrameStyle(QFrame.Panel)
@ -886,9 +884,9 @@ def supportText() -> str:
from aqt import mw from aqt import mw
if isWin: if isWin:
platname = "Windows " + platform.win32_ver()[0] platname = f"Windows {platform.win32_ver()[0]}"
elif isMac: elif isMac:
platname = "Mac " + platform.mac_ver()[0] platname = f"Mac {platform.mac_ver()[0]}"
else: else:
platname = "Linux" platname = "Linux"

View file

@ -97,7 +97,7 @@ class AnkiWebPage(QWebEnginePage):
else: else:
level_str = str(level) level_str = str(level)
buf = "JS %(t)s %(f)s:%(a)d %(b)s" % dict( 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: if "MathJax localStorage" in buf:
# silence localStorage noise # silence localStorage noise
@ -392,10 +392,10 @@ class AnkiWebView(QWebEngineView):
family = tr(TR.QT_MISC_SEGOE_UI) family = tr(TR.QT_MISC_SEGOE_UI)
button_style = "button { font-family:%s; }" % family button_style = "button { font-family:%s; }" % family
button_style += "\n:focus { outline: 1px solid %s; }" % color_hl 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: elif isMac:
family = "Helvetica" family = "Helvetica"
font = 'font-size:15px;font-family:"%s";' % family font = f'font-size:15px;font-family:"{family}";'
button_style = """ button_style = """
button { -webkit-appearance: none; background: #fff; border: 1px solid #ccc; button { -webkit-appearance: none; background: #fff; border: 1px solid #ccc;
border-radius:5px; font-family: Helvetica }""" border-radius:5px; font-family: Helvetica }"""
@ -403,7 +403,7 @@ border-radius:5px; font-family: Helvetica }"""
family = self.font().family() family = self.font().family()
color_hl_txt = palette.color(QPalette.HighlightedText).name() color_hl_txt = palette.color(QPalette.HighlightedText).name()
color_btn = palette.color(QPalette.Button).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 = """ button_style = """
/* Buttons */ /* Buttons */
button{ 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}" return f"http://127.0.0.1:{mw.mediaServer.getPort()}{subpath}{path}"
def bundledScript(self, fname: str) -> str: def bundledScript(self, fname: str) -> str:
return '<script src="%s"></script>' % self.webBundlePath(fname) return f'<script src="{self.webBundlePath(fname)}"></script>'
def bundledCSS(self, fname: str) -> str: def bundledCSS(self, fname: str) -> str:
return '<link rel="stylesheet" type="text/css" href="%s">' % self.webBundlePath( return '<link rel="stylesheet" type="text/css" href="%s">' % self.webBundlePath(
@ -641,5 +641,5 @@ document.head.appendChild(style);
else: else:
extra = "" extra = ""
self.hide_while_preserving_layout() 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() self.inject_dynamic_style_and_show()

View file

@ -86,7 +86,7 @@ def _err_unless_zero(result):
if result == 0: if result == 0:
return result return result
else: else:
raise WinPathsException("Failed to retrieve windows path: %s" % result) raise WinPathsException(f"Failed to retrieve windows path: {result}")
_SHGetFolderPath = windll.shell32.SHGetFolderPathW _SHGetFolderPath = windll.shell32.SHGetFolderPathW