# Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html """ Tools for extending Anki. A hook takes a function that does not return a value. A filter takes a function that returns its first argument, optionally modifying it. """ from __future__ import annotations from collections.abc import Callable from typing import Any import decorator # You can find the definitions in ../tools/genhooks.py from anki.hooks_gen import * # Legacy hook handling ############################################################################## _hooks: dict[str, list[Callable[..., Any]]] = {} def runHook(hook: str, *args: Any) -> None: "Run all functions on hook." hookFuncs = _hooks.get(hook, None) if hookFuncs: for func in hookFuncs: try: func(*args) except Exception: hookFuncs.remove(func) raise def runFilter(hook: str, arg: Any, *args: Any) -> Any: hookFuncs = _hooks.get(hook, None) if hookFuncs: for func in hookFuncs: try: arg = func(arg, *args) except Exception: hookFuncs.remove(func) raise return arg def addHook(hook: str, func: Callable) -> None: "Add a function to hook. Ignore if already on hook." if not _hooks.get(hook, None): _hooks[hook] = [] if func not in _hooks[hook]: _hooks[hook].append(func) def remHook(hook: Any, func: Any) -> None: "Remove a function if is on hook." hook = _hooks.get(hook, []) if func in hook: hook.remove(func) # Monkey patching ############################################################################## # Please only use this for prototyping or for when hooks are not practical, # as add-ons that use monkey patching are more likely to break when Anki is # updated. # # If you call wrap() with pos='around', the original function will not be called # automatically but can be called with _old(). def wrap(old: Any, new: Any, pos: str = "after") -> Callable: "Override an existing function." def repl(*args: Any, **kwargs: Any) -> Any: if pos == "after": old(*args, **kwargs) return new(*args, **kwargs) elif pos == "before": new(*args, **kwargs) return old(*args, **kwargs) else: return new(_old=old, *args, **kwargs) def decorator_wrapper(f: Any, *args: Any, **kwargs: Any) -> Any: return repl(*args, **kwargs) return decorator.decorator(decorator_wrapper)(old)