1# -*- coding: utf-8 -*-
2# Copyright: Ankitects Pty Ltd and contributors
3# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
4
5"""\
6Hooks - hook management and tools for extending Anki
7==============================================================================
8
9To find available hooks, grep for runHook and runFilter in the source code.
10
11Instrumenting allows you to modify functions that don't have hooks available.
12If you call wrap() with pos='around', the original function will not be called
13automatically but can be called with _old().
14"""
15
16import decorator
17
18# Hooks
19##############################################################################
20
21_hooks = {}
22
23def runHook(hook, *args):
24    "Run all functions on hook."
25    hook = _hooks.get(hook, None)
26    if hook:
27        for func in hook:
28            try:
29                func(*args)
30            except:
31                hook.remove(func)
32                raise
33
34def runFilter(hook, arg, *args):
35    hook = _hooks.get(hook, None)
36    if hook:
37        for func in hook:
38            try:
39                arg = func(arg, *args)
40            except:
41                hook.remove(func)
42                raise
43    return arg
44
45def addHook(hook, func):
46    "Add a function to hook. Ignore if already on hook."
47    if not _hooks.get(hook, None):
48        _hooks[hook] = []
49    if func not in _hooks[hook]:
50        _hooks[hook].append(func)
51
52def remHook(hook, func):
53    "Remove a function if is on hook."
54    hook = _hooks.get(hook, [])
55    if func in hook:
56        hook.remove(func)
57
58# Instrumenting
59##############################################################################
60
61def wrap(old, new, pos="after"):
62    "Override an existing function."
63    def repl(*args, **kwargs):
64        if pos == "after":
65            old(*args, **kwargs)
66            return new(*args, **kwargs)
67        elif pos == "before":
68            new(*args, **kwargs)
69            return old(*args, **kwargs)
70        else:
71            return new(_old=old, *args, **kwargs)
72
73    def decorator_wrapper(f, *args, **kwargs):
74        return repl(*args, **kwargs)
75
76    return decorator.decorator(decorator_wrapper)(old)
77