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:
Abdo 2021-12-22 05:51:18 +03:00 committed by GitHub
parent 12ccdee25b
commit 21fde1b59e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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."
if os.path.exists(path): self._config_help_actions[module] = action
with open(path, encoding="utf-8") as f:
return markdown.markdown(f.read(), extensions=["md_in_html"]) def addonConfigHelp(self, module: str) -> str:
if action := self._config_help_actions.get(module, None):
contents = action()
else: else:
return "" path = os.path.join(self.addonsFolder(module), "config.md")
if os.path.exists(path):
with open(path, encoding="utf-8") as f:
contents = f.read()
else:
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()