aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/docs/src/plugins/group/plugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'docs/src/plugins/group/plugin.py')
-rw-r--r--docs/src/plugins/group/plugin.py151
1 files changed, 151 insertions, 0 deletions
diff --git a/docs/src/plugins/group/plugin.py b/docs/src/plugins/group/plugin.py
new file mode 100644
index 00000000..4ab13dbf
--- /dev/null
+++ b/docs/src/plugins/group/plugin.py
@@ -0,0 +1,151 @@
+# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import logging
+
+from collections.abc import Callable
+from mkdocs.config.config_options import Plugins
+from mkdocs.config.defaults import MkDocsConfig
+from mkdocs.exceptions import PluginError
+from mkdocs.plugins import BasePlugin, event_priority
+
+from .config import GroupConfig
+
+# -----------------------------------------------------------------------------
+# Classes
+# -----------------------------------------------------------------------------
+
+# Group plugin
+class GroupPlugin(BasePlugin[GroupConfig]):
+ supports_multiple_instances = True
+
+ # Determine whether we're serving the site
+ def on_startup(self, *, command, dirty):
+ self.is_serve = command == "serve"
+ self.is_dirty = dirty
+
+ # If the group is enabled, conditionally load plugins - at first, this might
+ # sound easier than it actually is, as we need to jump through some hoops to
+ # ensure correct ordering among plugins. We're effectively initializing the
+ # plugins that are part of the group after all MkDocs finished initializing
+ # all other plugins, so we need to patch the order of the methods. Moreover,
+ # we must use MkDocs existing plugin collection, or we might have collisions
+ # with other plugins that are not part of the group. As so often, this is a
+ # little hacky, but has huge potential making plugin configuration easier.
+ # There's one little caveat: the `__init__` and `on_startup` methods of the
+ # plugins that are part of the group are called after all other plugins, so
+ # the `event_priority` decorator for `on_startup` events and is effectively
+ # useless. However, the `on_startup` event is only intended to set up the
+ # plugin and doesn't receive anything else than the invoked command and
+ # whether we're running a dirty build, so there should be no problems.
+ @event_priority(150)
+ def on_config(self, config):
+ if not self.config.enabled:
+ return
+
+ # Retrieve plugin collection from configuration
+ option: Plugins = dict(config._schema)["plugins"]
+ assert isinstance(option, Plugins)
+
+ # Load all plugins in group
+ self.plugins: dict[str, BasePlugin] = {}
+ try:
+ for name, plugin in self._load(option):
+ self.plugins[name] = plugin
+
+ # The plugin could not be loaded, likely because it's not installed or
+ # misconfigured, so we raise a plugin error for a nicer error message
+ except Exception as e:
+ raise PluginError(str(e))
+
+ # Patch order of plugin methods
+ for events in option.plugins.events.values():
+ self._patch(events, config)
+
+ # Invoke `on_startup` event for plugins in group
+ command = "serve" if self.is_serve else "build"
+ for method in option.plugins.events["startup"]:
+ plugin = self._get_plugin(method)
+
+ # Ensure that we have a method bound to a plugin (and not a hook)
+ if plugin and plugin in self.plugins.values():
+ method(command = command, dirty = self.is_dirty)
+
+ # -------------------------------------------------------------------------
+
+ # Retrieve plugin instance for bound method or nothing
+ def _get_plugin(self, method: Callable):
+ return getattr(method, "__self__", None)
+
+ # Retrieve priority of plugin method
+ def _get_priority(self, method: Callable):
+ return getattr(method, "mkdocs_priority", 0)
+
+ # Retrieve position of plugin
+ def _get_position(self, plugin: BasePlugin, config: MkDocsConfig) -> int:
+ for at, (_, candidate) in enumerate(config.plugins.items()):
+ if plugin == candidate:
+ return at
+
+ # -------------------------------------------------------------------------
+
+ # Load plugins that are part of the group
+ def _load(self, option: Plugins):
+ for name, data in option._parse_configs(self.config.plugins):
+ yield option.load_plugin_with_namespace(name, data)
+
+ # -------------------------------------------------------------------------
+
+ # Patch order of plugin methods - all other plugin methods are already in
+ # the right order, so we only need to check those that are part of the group
+ # and bubble them up into the right location. Some plugin methods may define
+ # priorities, so we need to make sure to order correctly within those.
+ def _patch(self, methods: list[Callable], config: MkDocsConfig):
+ position = self._get_position(self, config)
+ for at in reversed(range(1, len(methods))):
+ tail = methods[at - 1]
+ head = methods[at]
+
+ # Skip if the plugin is not part of the group
+ plugin = self._get_plugin(head)
+ if not plugin or plugin not in self.plugins.values():
+ continue
+
+ # Skip if the previous method has a higher priority than the current
+ # one, because we know we can't swap them anyway
+ if self._get_priority(tail) > self._get_priority(head):
+ continue
+
+ # Ensure that we have a method bound to a plugin (and not a hook)
+ plugin = self._get_plugin(tail)
+ if not plugin:
+ continue
+
+ # Both methods have the same priority, so we check if the ordering
+ # of both methods is violated, and if it is, swap them
+ if (position < self._get_position(plugin, config)):
+ methods[at], methods[at - 1] = tail, head
+
+# -----------------------------------------------------------------------------
+# Data
+# -----------------------------------------------------------------------------
+
+# Set up logging
+log = logging.getLogger("mkdocs.material.group")