mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Added experimental group plugin
This commit is contained in:
parent
b36af187a8
commit
4154a94956
19
material/plugins/group/__init__.py
Normal file
19
material/plugins/group/__init__.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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.
|
33
material/plugins/group/config.py
Normal file
33
material/plugins/group/config.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from mkdocs.config.config_options import Type
|
||||||
|
from mkdocs.config.base import Config
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Classes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Group plugin configuration
|
||||||
|
class GroupConfig(Config):
|
||||||
|
enabled = Type(bool, default = False)
|
||||||
|
plugins = Type(list | dict)
|
131
material/plugins/group/plugin.py
Normal file
131
material/plugins/group/plugin.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# 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.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] = {}
|
||||||
|
for name, plugin in self._load(option):
|
||||||
|
self.plugins[name] = plugin
|
||||||
|
|
||||||
|
# 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"]:
|
||||||
|
if method.__self__ in self.plugins.values():
|
||||||
|
method(command = command, dirty = self.is_dirty)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# 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 events - 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
|
||||||
|
if not head.__self__ 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
|
||||||
|
|
||||||
|
# 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(tail.__self__, config)):
|
||||||
|
methods[at], methods[at - 1] = tail, head
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Data
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log = logging.getLogger("mkdocs.material.group")
|
@ -58,6 +58,7 @@ Funding = "https://github.com/sponsors/squidfunk"
|
|||||||
|
|
||||||
[project.entry-points."mkdocs.plugins"]
|
[project.entry-points."mkdocs.plugins"]
|
||||||
"material/blog" = "material.plugins.blog.plugin:BlogPlugin"
|
"material/blog" = "material.plugins.blog.plugin:BlogPlugin"
|
||||||
|
"material/group" = "material.plugins.group.plugin:GroupPlugin"
|
||||||
"material/info" = "material.plugins.info.plugin:InfoPlugin"
|
"material/info" = "material.plugins.info.plugin:InfoPlugin"
|
||||||
"material/offline" = "material.plugins.offline.plugin:OfflinePlugin"
|
"material/offline" = "material.plugins.offline.plugin:OfflinePlugin"
|
||||||
"material/search" = "material.plugins.search.plugin:SearchPlugin"
|
"material/search" = "material.plugins.search.plugin:SearchPlugin"
|
||||||
|
19
src/plugins/group/__init__.py
Normal file
19
src/plugins/group/__init__.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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.
|
33
src/plugins/group/config.py
Normal file
33
src/plugins/group/config.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from mkdocs.config.config_options import Type
|
||||||
|
from mkdocs.config.base import Config
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Classes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Group plugin configuration
|
||||||
|
class GroupConfig(Config):
|
||||||
|
enabled = Type(bool, default = False)
|
||||||
|
plugins = Type(list | dict)
|
131
src/plugins/group/plugin.py
Normal file
131
src/plugins/group/plugin.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# 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.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] = {}
|
||||||
|
for name, plugin in self._load(option):
|
||||||
|
self.plugins[name] = plugin
|
||||||
|
|
||||||
|
# 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"]:
|
||||||
|
if method.__self__ in self.plugins.values():
|
||||||
|
method(command = command, dirty = self.is_dirty)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# 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 events - 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
|
||||||
|
if not head.__self__ 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
|
||||||
|
|
||||||
|
# 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(tail.__self__, config)):
|
||||||
|
methods[at], methods[at - 1] = tail, head
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Data
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log = logging.getLogger("mkdocs.material.group")
|
Loading…
Reference in New Issue
Block a user