124 lines
4.1 KiB
Python
124 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import Any, Optional
|
|
|
|
import jinja2
|
|
|
|
from mkdocs import localization, utils
|
|
from mkdocs.config.base import ValidationError
|
|
from mkdocs.utils import filters
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class Theme:
|
|
"""
|
|
A Theme object.
|
|
|
|
Keywords:
|
|
|
|
name: The name of the theme as defined by its entrypoint.
|
|
|
|
custom_dir: User defined directory for custom templates.
|
|
|
|
static_templates: A list of templates to render as static pages.
|
|
|
|
All other keywords are passed as-is and made available as a key/value mapping.
|
|
|
|
"""
|
|
|
|
def __init__(self, name: Optional[str] = None, **user_config) -> None:
|
|
self.name = name
|
|
self._vars = {'name': name, 'locale': 'en'}
|
|
|
|
# MkDocs provided static templates are always included
|
|
package_dir = os.path.abspath(os.path.dirname(__file__))
|
|
mkdocs_templates = os.path.join(package_dir, 'templates')
|
|
self.static_templates = set(os.listdir(mkdocs_templates))
|
|
|
|
# Build self.dirs from various sources in order of precedence
|
|
self.dirs = []
|
|
|
|
if 'custom_dir' in user_config:
|
|
self.dirs.append(user_config.pop('custom_dir'))
|
|
|
|
if name:
|
|
self._load_theme_config(name)
|
|
|
|
# Include templates provided directly by MkDocs (outside any theme)
|
|
self.dirs.append(mkdocs_templates)
|
|
|
|
# Handle remaining user configs. Override theme configs (if set)
|
|
self.static_templates.update(user_config.pop('static_templates', []))
|
|
self._vars.update(user_config)
|
|
|
|
# Validate locale and convert to Locale object
|
|
self._vars['locale'] = localization.parse_locale(self._vars['locale'])
|
|
|
|
def __repr__(self) -> str:
|
|
return "{}(name='{}', dirs={}, static_templates={}, {})".format(
|
|
self.__class__.__name__,
|
|
self.name,
|
|
self.dirs,
|
|
list(self.static_templates),
|
|
', '.join(f'{k}={v!r}' for k, v in self._vars.items()),
|
|
)
|
|
|
|
def __getitem__(self, key: str) -> Any:
|
|
return self._vars[key]
|
|
|
|
def __setitem__(self, key, value):
|
|
self._vars[key] = value
|
|
|
|
def __contains__(self, item: str) -> bool:
|
|
return item in self._vars
|
|
|
|
def __iter__(self):
|
|
return iter(self._vars)
|
|
|
|
def _load_theme_config(self, name: str) -> None:
|
|
"""Recursively load theme and any parent themes."""
|
|
|
|
theme_dir = utils.get_theme_dir(name)
|
|
self.dirs.append(theme_dir)
|
|
|
|
try:
|
|
file_path = os.path.join(theme_dir, 'mkdocs_theme.yml')
|
|
with open(file_path, 'rb') as f:
|
|
theme_config = utils.yaml_load(f)
|
|
if theme_config is None:
|
|
theme_config = {}
|
|
except OSError as e:
|
|
log.debug(e)
|
|
raise ValidationError(
|
|
f"The theme '{name}' does not appear to have a configuration file. "
|
|
f"Please upgrade to a current version of the theme."
|
|
)
|
|
|
|
log.debug(f"Loaded theme configuration for '{name}' from '{file_path}': {theme_config}")
|
|
|
|
parent_theme = theme_config.pop('extends', None)
|
|
if parent_theme:
|
|
themes = utils.get_theme_names()
|
|
if parent_theme not in themes:
|
|
raise ValidationError(
|
|
f"The theme '{name}' inherits from '{parent_theme}', which does not appear to be installed. "
|
|
f"The available installed themes are: {', '.join(themes)}"
|
|
)
|
|
self._load_theme_config(parent_theme)
|
|
|
|
self.static_templates.update(theme_config.pop('static_templates', []))
|
|
self._vars.update(theme_config)
|
|
|
|
def get_env(self) -> jinja2.Environment:
|
|
"""Return a Jinja environment for the theme."""
|
|
|
|
loader = jinja2.FileSystemLoader(self.dirs)
|
|
# No autoreload because editing a template in the middle of a build is not useful.
|
|
env = jinja2.Environment(loader=loader, auto_reload=False)
|
|
env.filters['url'] = filters.url_filter
|
|
localization.install_translations(env, self._vars['locale'], self.dirs)
|
|
return env
|