mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-04-06 06:48:32 +07:00
This is using the new "selection interface" ( by calling `entry_points` with the `group` parameter) that is being introduced in `importlib.metadata` of Python 3.10 (currently in alpha). While the backport (`importlib_metadata`) introduced the same change in version 3.6, various improvements have been made up through the current release (3.10). Therefore we require `importlib_metadata>=3.10` for all supported versions of Python (3.6-3.9).
101 lines
3.1 KiB
Python
101 lines
3.1 KiB
Python
"""
|
|
Implements the plugin API for MkDocs.
|
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
import importlib_metadata
|
|
from collections import OrderedDict
|
|
|
|
from mkdocs.config.base import Config
|
|
|
|
|
|
log = logging.getLogger('mkdocs.plugins')
|
|
|
|
|
|
EVENTS = (
|
|
'config', 'pre_build', 'files', 'nav', 'env', 'pre_template', 'template_context',
|
|
'post_template', 'pre_page', 'page_read_source', 'page_markdown',
|
|
'page_content', 'page_context', 'post_page', 'post_build', 'serve', 'build_error'
|
|
)
|
|
|
|
|
|
def get_plugins():
|
|
""" Return a dict of all installed Plugins as {name: EntryPoint}. """
|
|
|
|
plugins = importlib_metadata.entry_points(group='mkdocs.plugins')
|
|
|
|
return {plugin.name: plugin for plugin in plugins}
|
|
|
|
|
|
class BasePlugin:
|
|
"""
|
|
Plugin base class.
|
|
|
|
All plugins should subclass this class.
|
|
"""
|
|
|
|
config_scheme = ()
|
|
config = {}
|
|
|
|
def load_config(self, options, config_file_path=None):
|
|
""" Load config from a dict of options. Returns a tuple of (errors, warnings)."""
|
|
|
|
self.config = Config(schema=self.config_scheme, config_file_path=config_file_path)
|
|
self.config.load_dict(options)
|
|
|
|
return self.config.validate()
|
|
|
|
|
|
class PluginCollection(OrderedDict):
|
|
"""
|
|
A collection of plugins.
|
|
|
|
In addition to being a dict of Plugin instances, each event method is registered
|
|
upon being added. All registered methods for a given event can then be run in order
|
|
by calling `run_event`.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.events = {x: [] for x in EVENTS}
|
|
|
|
def _register_event(self, event_name, method):
|
|
""" Register a method for an event. """
|
|
self.events[event_name].append(method)
|
|
|
|
def __setitem__(self, key, value, **kwargs):
|
|
if not isinstance(value, BasePlugin):
|
|
raise TypeError(
|
|
'{0}.{1} only accepts values which are instances of {2}.{3} '
|
|
'sublcasses'.format(self.__module__, self.__name__,
|
|
BasePlugin.__module__, BasePlugin.__name__))
|
|
super().__setitem__(key, value, **kwargs)
|
|
# Register all of the event methods defined for this Plugin.
|
|
for event_name in (x for x in dir(value) if x.startswith('on_')):
|
|
method = getattr(value, event_name)
|
|
if callable(method):
|
|
self._register_event(event_name[3:], method)
|
|
|
|
def run_event(self, name, item=None, **kwargs):
|
|
"""
|
|
Run all registered methods of an event.
|
|
|
|
`item` is the object to be modified or replaced and returned by the event method.
|
|
If it isn't given the event method creates a new object to be returned.
|
|
All other keywords are variables for context, but would not generally
|
|
be modified by the event method.
|
|
"""
|
|
|
|
pass_item = item is not None
|
|
for method in self.events[name]:
|
|
if pass_item:
|
|
result = method(item, **kwargs)
|
|
else:
|
|
result = method(**kwargs)
|
|
# keep item if method returned `None`
|
|
if result is not None:
|
|
item = result
|
|
return item
|