mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32:22 -04:00
Allow customization of add-on config help path (#1564)
* Allow customization of add-on config help path This is useful for loading translated versions of the help file if available * dir -> module * Allow setting a callback instead to produce config docs
This commit is contained in:
parent
12ccdee25b
commit
21fde1b59e
1 changed files with 64 additions and 54 deletions
112
qt/aqt/addons.py
112
qt/aqt/addons.py
|
@ -222,11 +222,11 @@ class AddonManager:
|
||||||
def all_addon_meta(self) -> Iterable[AddonMeta]:
|
def all_addon_meta(self) -> Iterable[AddonMeta]:
|
||||||
return map(self.addon_meta, self.allAddons())
|
return map(self.addon_meta, self.allAddons())
|
||||||
|
|
||||||
def addonsFolder(self, dir: str | None = None) -> str:
|
def addonsFolder(self, module: str | None = None) -> str:
|
||||||
root = self.mw.pm.addonFolder()
|
root = self.mw.pm.addonFolder()
|
||||||
if dir is None:
|
if module is None:
|
||||||
return root
|
return root
|
||||||
return os.path.join(root, dir)
|
return os.path.join(root, module)
|
||||||
|
|
||||||
def loadAddons(self) -> None:
|
def loadAddons(self) -> None:
|
||||||
for addon in self.all_addon_meta():
|
for addon in self.all_addon_meta():
|
||||||
|
@ -276,33 +276,33 @@ class AddonManager:
|
||||||
|
|
||||||
self.writeAddonMeta(addon.dir_name, json_obj)
|
self.writeAddonMeta(addon.dir_name, json_obj)
|
||||||
|
|
||||||
def _addonMetaPath(self, dir: str) -> str:
|
def _addonMetaPath(self, module: str) -> str:
|
||||||
return os.path.join(self.addonsFolder(dir), "meta.json")
|
return os.path.join(self.addonsFolder(module), "meta.json")
|
||||||
|
|
||||||
# in new code, use self.addon_meta() instead
|
# in new code, use self.addon_meta() instead
|
||||||
def addonMeta(self, dir: str) -> dict[str, Any]:
|
def addonMeta(self, module: str) -> dict[str, Any]:
|
||||||
path = self._addonMetaPath(dir)
|
path = self._addonMetaPath(module)
|
||||||
try:
|
try:
|
||||||
with open(path, encoding="utf8") as f:
|
with open(path, encoding="utf8") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
print(f"json error in add-on {dir}:\n{e}")
|
print(f"json error in add-on {module}:\n{e}")
|
||||||
return dict()
|
return dict()
|
||||||
except:
|
except:
|
||||||
# missing meta file, etc
|
# missing meta file, etc
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
# in new code, use write_addon_meta() instead
|
# in new code, use write_addon_meta() instead
|
||||||
def writeAddonMeta(self, dir: str, meta: dict[str, Any]) -> None:
|
def writeAddonMeta(self, module: str, meta: dict[str, Any]) -> None:
|
||||||
path = self._addonMetaPath(dir)
|
path = self._addonMetaPath(module)
|
||||||
with open(path, "w", encoding="utf8") as f:
|
with open(path, "w", encoding="utf8") as f:
|
||||||
json.dump(meta, f)
|
json.dump(meta, f)
|
||||||
|
|
||||||
def toggleEnabled(self, dir: str, enable: bool | None = None) -> None:
|
def toggleEnabled(self, module: str, enable: bool | None = None) -> None:
|
||||||
addon = self.addon_meta(dir)
|
addon = self.addon_meta(module)
|
||||||
should_enable = enable if enable is not None else not addon.enabled
|
should_enable = enable if enable is not None else not addon.enabled
|
||||||
if should_enable is True:
|
if should_enable is True:
|
||||||
conflicting = self._disableConflicting(dir)
|
conflicting = self._disableConflicting(module)
|
||||||
if conflicting:
|
if conflicting:
|
||||||
addons = ", ".join(self.addonName(f) for f in conflicting)
|
addons = ", ".join(self.addonName(f) for f in conflicting)
|
||||||
showInfo(
|
showInfo(
|
||||||
|
@ -326,17 +326,17 @@ class AddonManager:
|
||||||
# Legacy helpers
|
# Legacy helpers
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def isEnabled(self, dir: str) -> bool:
|
def isEnabled(self, module: str) -> bool:
|
||||||
return self.addon_meta(dir).enabled
|
return self.addon_meta(module).enabled
|
||||||
|
|
||||||
def addonName(self, dir: str) -> str:
|
def addonName(self, module: str) -> str:
|
||||||
return self.addon_meta(dir).human_name()
|
return self.addon_meta(module).human_name()
|
||||||
|
|
||||||
def addonConflicts(self, dir: str) -> list[str]:
|
def addonConflicts(self, module: str) -> list[str]:
|
||||||
return self.addon_meta(dir).conflicts
|
return self.addon_meta(module).conflicts
|
||||||
|
|
||||||
def annotatedName(self, dir: str) -> str:
|
def annotatedName(self, module: str) -> str:
|
||||||
meta = self.addon_meta(dir)
|
meta = self.addon_meta(module)
|
||||||
name = meta.human_name()
|
name = meta.human_name()
|
||||||
if not meta.enabled:
|
if not meta.enabled:
|
||||||
name += f" {tr.addons_disabled()}"
|
name += f" {tr.addons_disabled()}"
|
||||||
|
@ -354,12 +354,12 @@ class AddonManager:
|
||||||
all_conflicts[other_dir].append(addon.dir_name)
|
all_conflicts[other_dir].append(addon.dir_name)
|
||||||
return all_conflicts
|
return all_conflicts
|
||||||
|
|
||||||
def _disableConflicting(self, dir: str, conflicts: list[str] = None) -> set[str]:
|
def _disableConflicting(self, module: str, conflicts: list[str] = None) -> set[str]:
|
||||||
conflicts = conflicts or self.addonConflicts(dir)
|
conflicts = conflicts or self.addonConflicts(module)
|
||||||
|
|
||||||
installed = self.allAddons()
|
installed = self.allAddons()
|
||||||
found = {d for d in conflicts if d in installed and self.isEnabled(d)}
|
found = {d for d in conflicts if d in installed and self.isEnabled(d)}
|
||||||
found.update(self.allAddonConflicts().get(dir, []))
|
found.update(self.allAddonConflicts().get(module, []))
|
||||||
|
|
||||||
for package in found:
|
for package in found:
|
||||||
self.toggleEnabled(package, enable=False)
|
self.toggleEnabled(package, enable=False)
|
||||||
|
@ -421,17 +421,17 @@ class AddonManager:
|
||||||
name=meta["name"], conflicts=found_conflicts, compatible=meta2.compatible()
|
name=meta["name"], conflicts=found_conflicts, compatible=meta2.compatible()
|
||||||
)
|
)
|
||||||
|
|
||||||
def _install(self, dir: str, zfile: ZipFile) -> None:
|
def _install(self, module: str, zfile: ZipFile) -> None:
|
||||||
# previously installed?
|
# previously installed?
|
||||||
base = self.addonsFolder(dir)
|
base = self.addonsFolder(module)
|
||||||
if os.path.exists(base):
|
if os.path.exists(base):
|
||||||
self.backupUserFiles(dir)
|
self.backupUserFiles(module)
|
||||||
if not self.deleteAddon(dir):
|
if not self.deleteAddon(module):
|
||||||
self.restoreUserFiles(dir)
|
self.restoreUserFiles(module)
|
||||||
return
|
return
|
||||||
|
|
||||||
os.mkdir(base)
|
os.mkdir(base)
|
||||||
self.restoreUserFiles(dir)
|
self.restoreUserFiles(module)
|
||||||
|
|
||||||
# extract
|
# extract
|
||||||
for n in zfile.namelist():
|
for n in zfile.namelist():
|
||||||
|
@ -446,9 +446,9 @@ class AddonManager:
|
||||||
zfile.extract(n, base)
|
zfile.extract(n, base)
|
||||||
|
|
||||||
# true on success
|
# true on success
|
||||||
def deleteAddon(self, dir: str) -> bool:
|
def deleteAddon(self, module: str) -> bool:
|
||||||
try:
|
try:
|
||||||
send2trash(self.addonsFolder(dir))
|
send2trash(self.addonsFolder(module))
|
||||||
return True
|
return True
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
showWarning(
|
showWarning(
|
||||||
|
@ -600,40 +600,50 @@ class AddonManager:
|
||||||
|
|
||||||
_configButtonActions: dict[str, Callable[[], bool | None]] = {}
|
_configButtonActions: dict[str, Callable[[], bool | None]] = {}
|
||||||
_configUpdatedActions: dict[str, Callable[[Any], None]] = {}
|
_configUpdatedActions: dict[str, Callable[[Any], None]] = {}
|
||||||
|
_config_help_actions: dict[str, Callable[[], str]] = {}
|
||||||
|
|
||||||
def addonConfigDefaults(self, dir: str) -> dict[str, Any] | None:
|
def addonConfigDefaults(self, module: str) -> dict[str, Any] | None:
|
||||||
path = os.path.join(self.addonsFolder(dir), "config.json")
|
path = os.path.join(self.addonsFolder(module), "config.json")
|
||||||
try:
|
try:
|
||||||
with open(path, encoding="utf8") as f:
|
with open(path, encoding="utf8") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def addonConfigHelp(self, dir: str) -> str:
|
def set_config_help_action(self, module: str, action: Callable[[], str]) -> None:
|
||||||
path = os.path.join(self.addonsFolder(dir), "config.md")
|
"Set a callback used to produce config help."
|
||||||
|
self._config_help_actions[module] = action
|
||||||
|
|
||||||
|
def addonConfigHelp(self, module: str) -> str:
|
||||||
|
if action := self._config_help_actions.get(module, None):
|
||||||
|
contents = action()
|
||||||
|
else:
|
||||||
|
path = os.path.join(self.addonsFolder(module), "config.md")
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
with open(path, encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
return markdown.markdown(f.read(), extensions=["md_in_html"])
|
contents = f.read()
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
return markdown.markdown(contents, extensions=["md_in_html"])
|
||||||
|
|
||||||
def addonFromModule(self, module: str) -> str:
|
def addonFromModule(self, module: str) -> str:
|
||||||
return module.split(".")[0]
|
return module.split(".")[0]
|
||||||
|
|
||||||
def configAction(self, addon: str) -> Callable[[], bool | None]:
|
def configAction(self, module: str) -> Callable[[], bool | None]:
|
||||||
return self._configButtonActions.get(addon)
|
return self._configButtonActions.get(module)
|
||||||
|
|
||||||
def configUpdatedAction(self, addon: str) -> Callable[[Any], None]:
|
def configUpdatedAction(self, module: str) -> Callable[[Any], None]:
|
||||||
return self._configUpdatedActions.get(addon)
|
return self._configUpdatedActions.get(module)
|
||||||
|
|
||||||
# Schema
|
# Schema
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def _addon_schema_path(self, dir: str) -> str:
|
def _addon_schema_path(self, module: str) -> str:
|
||||||
return os.path.join(self.addonsFolder(dir), "config.schema.json")
|
return os.path.join(self.addonsFolder(module), "config.schema.json")
|
||||||
|
|
||||||
def _addon_schema(self, dir: str) -> Any:
|
def _addon_schema(self, module: str) -> Any:
|
||||||
path = self._addon_schema_path(dir)
|
path = self._addon_schema_path(module)
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
# True is a schema accepting everything
|
# True is a schema accepting everything
|
||||||
|
@ -704,8 +714,8 @@ class AddonManager:
|
||||||
addon = self.addonFromModule(module)
|
addon = self.addonFromModule(module)
|
||||||
self._webExports[addon] = pattern
|
self._webExports[addon] = pattern
|
||||||
|
|
||||||
def getWebExports(self, addon: str) -> str:
|
def getWebExports(self, module: str) -> str:
|
||||||
return self._webExports.get(addon)
|
return self._webExports.get(module)
|
||||||
|
|
||||||
|
|
||||||
# Add-ons Dialog
|
# Add-ons Dialog
|
||||||
|
@ -842,8 +852,8 @@ class AddonsDialog(QDialog):
|
||||||
return self.addons[idxs[0]]
|
return self.addons[idxs[0]]
|
||||||
|
|
||||||
def onToggleEnabled(self) -> None:
|
def onToggleEnabled(self) -> None:
|
||||||
for dir in self.selectedAddons():
|
for module in self.selectedAddons():
|
||||||
self.mgr.toggleEnabled(dir)
|
self.mgr.toggleEnabled(module)
|
||||||
self.redrawAddons()
|
self.redrawAddons()
|
||||||
|
|
||||||
def onViewPage(self) -> None:
|
def onViewPage(self) -> None:
|
||||||
|
@ -874,8 +884,8 @@ class AddonsDialog(QDialog):
|
||||||
if not askUser(tr.addons_delete_the_numd_selected_addon(count=len(selected))):
|
if not askUser(tr.addons_delete_the_numd_selected_addon(count=len(selected))):
|
||||||
return
|
return
|
||||||
gui_hooks.addons_dialog_will_delete_addons(self, selected)
|
gui_hooks.addons_dialog_will_delete_addons(self, selected)
|
||||||
for dir in selected:
|
for module in selected:
|
||||||
if not self.mgr.deleteAddon(dir):
|
if not self.mgr.deleteAddon(module):
|
||||||
break
|
break
|
||||||
self.form.addonList.clearSelection()
|
self.form.addonList.clearSelection()
|
||||||
self.redrawAddons()
|
self.redrawAddons()
|
||||||
|
|
Loading…
Reference in a new issue