diff --git a/docs/dev-guide/themes.md b/docs/dev-guide/themes.md index 6acd36d7..8adac115 100644 --- a/docs/dev-guide/themes.md +++ b/docs/dev-guide/themes.md @@ -315,7 +315,7 @@ this can be used directly by prepending it to a local relative URL, it is best to use the [url](#url) template filter, which is smarter about how it applies `base_url`. -#### mkdocs_version +#### properdocs_version Contains the current ProperDocs version. diff --git a/properdocs/__main__.py b/properdocs/__main__.py index 2b851a0e..a3d9c7b3 100644 --- a/properdocs/__main__.py +++ b/properdocs/__main__.py @@ -12,6 +12,7 @@ import click +from properdocs import replacement # noqa: F401 from properdocs import __version__, config, utils if sys.platform.startswith("win"): diff --git a/properdocs/commands/build.py b/properdocs/commands/build.py index fb9de37e..217a38d2 100644 --- a/properdocs/commands/build.py +++ b/properdocs/commands/build.py @@ -51,7 +51,8 @@ def get_context( base_url=base_url, extra_css=extra_css, extra_javascript=extra_javascript, - mkdocs_version=properdocs.__version__, + properdocs_version=properdocs.__version__, + mkdocs_version=f"ProperDocs {properdocs.__version__}", build_date_utc=utils.get_build_datetime(), config=config, page=page, diff --git a/properdocs/config/defaults.py b/properdocs/config/defaults.py index b306137e..a14fc4bf 100644 --- a/properdocs/config/defaults.py +++ b/properdocs/config/defaults.py @@ -213,6 +213,9 @@ def load_file(self, config_file: IO) -> None: self.load_dict(yaml_load(config_file, loader)) +MkDocsConfig = ProperDocsConfig # Legacy alias + + def get_schema() -> base.PlainConfigSchema: """Soft-deprecated, do not use.""" return ProperDocsConfig._schema diff --git a/properdocs/exceptions.py b/properdocs/exceptions.py index 091be448..0cb99c88 100644 --- a/properdocs/exceptions.py +++ b/properdocs/exceptions.py @@ -10,6 +10,9 @@ class ProperDocsException(ClickException): """ +MkDocsException = ProperDocsException # Legacy alias + + class Abort(ProperDocsException, SystemExit): """Abort the build.""" diff --git a/properdocs/plugins.py b/properdocs/plugins.py index 0bd5a9df..e121d373 100644 --- a/properdocs/plugins.py +++ b/properdocs/plugins.py @@ -45,17 +45,18 @@ def get_plugins() -> dict[str, EntryPoint]: """Return a dict of all installed Plugins as {name: EntryPoint}.""" - plugins = entry_points(group='properdocs.plugins') - - # Allow third-party plugins to override core plugins - pluginmap = {} - for plugin in plugins: - if plugin.name in pluginmap and plugin.value.startswith("properdocs.contrib."): - continue - - pluginmap[plugin.name] = plugin - - return pluginmap + pluginmaps = {'properdocs': {}, 'mkdocs': {}} + + for prefix in pluginmaps: + for plugin in entry_points(group=f'{prefix}.plugins'): + if getattr(plugin, 'value', '').startswith('mkdocs.'): + continue + # Allow third-party plugins to override core plugins + if plugin.name in pluginmaps[prefix] and plugin.value.startswith(f"{prefix}.contrib."): + continue + pluginmaps[prefix][plugin.name] = plugin + + return pluginmaps['mkdocs'] | pluginmaps['properdocs'] SomeConfig = TypeVar('SomeConfig', bound=Config) diff --git a/properdocs/replacement.py b/properdocs/replacement.py new file mode 100644 index 00000000..ef68c6b3 --- /dev/null +++ b/properdocs/replacement.py @@ -0,0 +1,42 @@ +"""After this file is imported, all mkdocs.* imports get redirected to properdocs.* imports.""" + +import importlib.abc +import importlib.util +import sys + + +class _AliasLoader(importlib.abc.Loader): + """Loads the module with the given name and replaces the passed spec's module.""" + + def __init__(self, realname): + self.realname = realname + + def create_module(self, spec): + module = importlib.import_module(self.realname) + sys.modules[spec.name] = module + return module + + def exec_module(self, module): + pass + + +class _AliasFinder: + """When searching for any mkdocs.* module, find the corresponding properdocs.* module instead.""" + + def find_spec(self, fullname, path, target=None): + if fullname.startswith("mkdocs."): + realname = "properdocs." + fullname.removeprefix("mkdocs.") + spec = importlib.util.find_spec(realname) + if spec is None: + raise ImportError(f"No module named {realname!r}") + return importlib.util.spec_from_loader( + fullname, + _AliasLoader(realname), + is_package=spec.submodule_search_locations is not None, + ) + return None + + +sys.meta_path.insert(0, _AliasFinder()) +# Plus, handle the topmost module directly and without waiting for it to be requested. +sys.modules['mkdocs'] = sys.modules['properdocs'] diff --git a/properdocs/tests/config/config_options_tests.py b/properdocs/tests/config/config_options_tests.py index 0c14d3e6..b4298fdb 100644 --- a/properdocs/tests/config/config_options_tests.py +++ b/properdocs/tests/config/config_options_tests.py @@ -1939,6 +1939,7 @@ class ThemePlugin2(BasePlugin[_FakePluginConfig]): class FakeEntryPoint: def __init__(self, name, cls): self.name = name + self.value = 'properdocs' self.cls = cls def load(self): diff --git a/properdocs/utils/__init__.py b/properdocs/utils/__init__.py index 16f568e4..76cf386e 100644 --- a/properdocs/utils/__init__.py +++ b/properdocs/utils/__init__.py @@ -262,10 +262,21 @@ def get_theme_dir(name: str) -> str: @functools.lru_cache(maxsize=None) def get_themes() -> dict[str, EntryPoint]: """Return a dict of all installed themes as {name: EntryPoint}.""" - themes: dict[str, EntryPoint] = {} - eps: dict[EntryPoint, None] = dict.fromkeys(entry_points(group='properdocs.themes')) - builtins = {ep.name for ep in eps if ep.dist is not None and ep.dist.name == 'properdocs'} + # Ordered set of preferred entry points. + eps: dict[EntryPoint, None] = {} + builtins: set[str] = set() + + for ep in entry_points(group='mkdocs.themes'): + if ep.dist is not None and ep.dist.name != 'mkdocs': + eps[ep] = None + # These will get preference because they are later in the sequence: + for ep in entry_points(group='properdocs.themes'): + if ep.dist is not None: + eps[ep] = None + if ep.dist.name == 'properdocs': + builtins.add(ep.name) + themes: dict[str, EntryPoint] = {} for theme in eps: assert theme.dist is not None diff --git a/properdocs/utils/templates.py b/properdocs/utils/templates.py index 0b6a6c20..1700f4d3 100644 --- a/properdocs/utils/templates.py +++ b/properdocs/utils/templates.py @@ -28,6 +28,7 @@ class TemplateContext(TypedDict): base_url: str extra_css: Sequence[str] # Do not use, prefer `config.extra_css`. extra_javascript: Sequence[str] # Do not use, prefer `config.extra_javascript`. + properdocs_version: str mkdocs_version: str build_date_utc: datetime.datetime config: ProperDocsConfig