From 5b354164c5c4fbf6af1d58c185c7cd0510aedcbc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 10 Dec 2018 12:30:47 +0100 Subject: [PATCH] Make it possible for extensions to define init hooks --- qutebrowser/api/apitypes.py | 1 + qutebrowser/api/hook.py | 35 +++++++++++++++++++++++++ qutebrowser/components/__init__.py | 2 +- qutebrowser/components/caretcommands.py | 2 +- qutebrowser/extensions/loader.py | 35 ++++++++++++++++++++++++- 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 qutebrowser/api/hook.py diff --git a/qutebrowser/api/apitypes.py b/qutebrowser/api/apitypes.py index 9fec0a6cb..8fbc1a9a7 100644 --- a/qutebrowser/api/apitypes.py +++ b/qutebrowser/api/apitypes.py @@ -24,3 +24,4 @@ from qutebrowser.browser.browsertab import WebTabError, AbstractTab as Tab from qutebrowser.browser.webelem import (Error as WebElemError, AbstractWebElement as WebElement) from qutebrowser.utils.usertypes import ClickTarget, JsWorld +from qutebrowser.extensions.loader import InitContext diff --git a/qutebrowser/api/hook.py b/qutebrowser/api/hook.py new file mode 100644 index 000000000..b468e91f5 --- /dev/null +++ b/qutebrowser/api/hook.py @@ -0,0 +1,35 @@ +# Copyright 2018 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Hooks for extensions.""" + +import importlib +import types +import typing + + +from qutebrowser.extensions import loader + + +class init: # noqa: N801,N806 pylint: disable=invalid-name + + """Decorator to mark a function to run when initializing.""" + + def __call__(self, func: typing.Callable) -> typing.Callable: + module = importlib.import_module(func.__module__) + info = loader.add_module_info(module) + info.init_hook = func diff --git a/qutebrowser/components/__init__.py b/qutebrowser/components/__init__.py index b42c87fb6..1a13763bf 100644 --- a/qutebrowser/components/__init__.py +++ b/qutebrowser/components/__init__.py @@ -17,4 +17,4 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""qutebrowser "extensions" which only use the qutebrowser.API API.""" +"""qutebrowser "extensions" which only use the qutebrowser.api API.""" diff --git a/qutebrowser/components/caretcommands.py b/qutebrowser/components/caretcommands.py index 4bab6b6c6..b9ecfab7c 100644 --- a/qutebrowser/components/caretcommands.py +++ b/qutebrowser/components/caretcommands.py @@ -20,7 +20,7 @@ """Commands related to caret browsing.""" -from qutebrowser.api import cmdutils, apitypes +from qutebrowser.api import cmdutils, apitypes, hook @cmdutils.register(modes=[cmdutils.KeyMode.caret]) diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py index b2481b969..f8cafa9e3 100644 --- a/qutebrowser/extensions/loader.py +++ b/qutebrowser/extensions/loader.py @@ -31,6 +31,23 @@ from qutebrowser import components from qutebrowser.utils import log +@attr.s +class InitContext: + + """Context an extension gets in its init hook.""" + + +@attr.s +class ModuleInfo: + + """Information attached to an extension module. + + This gets used by qutebrowser.api.hook. + """ + + init_hook = attr.ib(None) # type: typing.Optional[typing.Callable] + + @attr.s class ExtensionInfo: @@ -39,6 +56,13 @@ class ExtensionInfo: name = attr.ib() # type: str +def add_module_info(module: types.ModuleType) -> ModuleInfo: + """Add ModuleInfo to a module (if not added yet).""" + if not hasattr(module, '__qute_module_info'): + module.__qute_module_info = ModuleInfo() + return module.__qute_module_info + + def load_components() -> None: """Load everything from qutebrowser.components.""" for info in walk_components(): @@ -88,5 +112,14 @@ def _walk_pyinstaller() -> typing.Iterator[ExtensionInfo]: def _load_component(info: ExtensionInfo) -> types.ModuleType: + """Load the given extension and run its init hook (if any).""" log.extensions.debug("Importing {}".format(info.name)) - return importlib.import_module(info.name) + mod = importlib.import_module(info.name) + + info = add_module_info(mod) + if info.init_hook is not None: + log.extensions.debug("Running init hook {!r}" + .format(info.init_hook.__name__)) + info.init_hook(InitContext()) + + return mod