theme.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import os
  2. import jinja2
  3. import logging
  4. from mkdocs import utils
  5. from mkdocs.utils import filters
  6. from mkdocs.config.base import ValidationError
  7. log = logging.getLogger(__name__)
  8. log.addFilter(utils.warning_filter)
  9. class Theme:
  10. """
  11. A Theme object.
  12. Keywords:
  13. name: The name of the theme as defined by its entrypoint.
  14. custom_dir: User defined directory for custom templates.
  15. static_templates: A list of templates to render as static pages.
  16. All other keywords are passed as-is and made available as a key/value mapping.
  17. """
  18. def __init__(self, name=None, **user_config):
  19. self.name = name
  20. self._vars = {}
  21. # MkDocs provided static templates are always included
  22. package_dir = os.path.abspath(os.path.dirname(__file__))
  23. mkdocs_templates = os.path.join(package_dir, 'templates')
  24. self.static_templates = set(os.listdir(mkdocs_templates))
  25. # Build self.dirs from various sources in order of precedence
  26. self.dirs = []
  27. if 'custom_dir' in user_config:
  28. self.dirs.append(user_config.pop('custom_dir'))
  29. if self.name:
  30. self._load_theme_config(name)
  31. # Include templates provided directly by MkDocs (outside any theme)
  32. self.dirs.append(mkdocs_templates)
  33. # Handle remaining user configs. Override theme configs (if set)
  34. self.static_templates.update(user_config.pop('static_templates', []))
  35. self._vars.update(user_config)
  36. def __repr__(self):
  37. return "{}(name='{}', dirs={}, static_templates={}, {})".format(
  38. self.__class__.__name__, self.name, self.dirs, list(self.static_templates),
  39. ', '.join('{}={}'.format(k, repr(v)) for k, v in self._vars.items())
  40. )
  41. def __getitem__(self, key):
  42. return self._vars[key]
  43. def __setitem__(self, key, value):
  44. self._vars[key] = value
  45. def __contains__(self, item):
  46. return item in self._vars
  47. def __iter__(self):
  48. return iter(self._vars)
  49. def _load_theme_config(self, name):
  50. """ Recursively load theme and any parent themes. """
  51. theme_dir = utils.get_theme_dir(name)
  52. self.dirs.append(theme_dir)
  53. try:
  54. file_path = os.path.join(theme_dir, 'mkdocs_theme.yml')
  55. with open(file_path, 'rb') as f:
  56. theme_config = utils.yaml_load(f)
  57. if theme_config is None:
  58. theme_config = {}
  59. except OSError as e:
  60. log.debug(e)
  61. raise ValidationError(
  62. "The theme '{}' does not appear to have a configuration file. "
  63. "Please upgrade to a current version of the theme.".format(name)
  64. )
  65. log.debug("Loaded theme configuration for '%s' from '%s': %s", name, file_path, theme_config)
  66. parent_theme = theme_config.pop('extends', None)
  67. if parent_theme:
  68. themes = utils.get_theme_names()
  69. if parent_theme not in themes:
  70. raise ValidationError(
  71. "The theme '{}' inherits from '{}', which does not appear to be installed. "
  72. "The available installed themes are: {}".format(name, parent_theme, ', '.join(themes))
  73. )
  74. self._load_theme_config(parent_theme)
  75. self.static_templates.update(theme_config.pop('static_templates', []))
  76. self._vars.update(theme_config)
  77. def get_env(self):
  78. """ Return a Jinja environment for the theme. """
  79. loader = jinja2.FileSystemLoader(self.dirs)
  80. env = jinja2.Environment(loader=loader)
  81. env.filters['tojson'] = filters.tojson
  82. env.filters['url'] = filters.url_filter
  83. return env