Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/dev-guide/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ appropriate package name and install it using `pip`:
pip install mkdocs-foo-plugin
```

WARNING: Installing an MkDocs plugin means installing a Python package and executing any code that the author has put in there. So, exercise the usual caution; there's no attempt at sandboxing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
WARNING: Installing an MkDocs plugin means installing a Python package and executing any code that the author has put in there. So, exercise the usual caution; there's no attempt at sandboxing.
WARNING: By installing an MkDocs plugin you are installing a Python package and executing any code embedded by the author. There's no attempt at sandboxing, so exercise the usual caution.

This comment was marked as abuse.


Once a plugin has been successfully installed, it is ready to use. It just needs
to be [enabled](#using-plugins) in the configuration file. The [Catalog]
repository has a large ranked list of plugins that you can install and use.
Expand Down
2 changes: 2 additions & 0 deletions docs/user-guide/choosing-your-theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ theme supports the following options:

A list of third party themes can be found at the [community wiki] page and [the ranked catalog][catalog]. If you have created your own, please add them there.

WARNING: Installing an MkDocs theme means installing a Python package and executing any code that the author has put in there. So, exercise the usual caution; there's no attempt at sandboxing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
WARNING: Installing an MkDocs theme means installing a Python package and executing any code that the author has put in there. So, exercise the usual caution; there's no attempt at sandboxing.
WARNING: By installing an MkDocs plugin you are installing a Python package and executing any code embedded by the author. There's no attempt at sandboxing, so exercise the usual caution.


[third party themes]: #third-party-themes
[theme]: configuration.md#theme
[Bootstrap]: https://getbootstrap.com/
Expand Down
4 changes: 2 additions & 2 deletions mkdocs/tests/theme_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def test_vars(self):
self.assertTrue('new' in theme)
self.assertEqual(theme['new'], 42)

@mock.patch('mkdocs.utils.yaml_load', return_value={})
@mock.patch('yaml.load', return_value={})
def test_no_theme_config(self, m):
theme = Theme(name='mkdocs')
self.assertEqual(m.call_count, 1)
Expand All @@ -89,7 +89,7 @@ def test_inherited_theme(self):
{'static_templates': ['parent.html']},
]
)
with mock.patch('mkdocs.utils.yaml_load', m) as m:
with mock.patch('yaml.load', m) as m:
theme = Theme(name='mkdocs')
self.assertEqual(m.call_count, 2)
self.assertEqual(
Expand Down
161 changes: 83 additions & 78 deletions mkdocs/tests/utils/utils_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,84 +203,6 @@ def test_insort_key(self):
utils.insort(a, (1, 'a'), key=lambda v: v[0])
self.assertEqual(a, [(1, 'a'), (1, 'b'), (1, 'a'), (2, 'c')])

def test_get_themes(self):
themes = utils.get_theme_names()
self.assertIn('mkdocs', themes)
self.assertIn('readthedocs', themes)

@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_theme_dir(self, mock_iter):
path = 'some/path'

theme = mock.Mock()
theme.name = 'mkdocs2'
theme.dist.name = 'mkdocs2'
theme.load().__file__ = os.path.join(path, '__init__.py')

mock_iter.return_value = [theme]

self.assertEqual(utils.get_theme_dir(theme.name), os.path.abspath(path))

def test_get_theme_dir_keyerror(self):
with self.assertRaises(KeyError):
utils.get_theme_dir('nonexistanttheme')

@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_theme_dir_importerror(self, mock_iter):
theme = mock.Mock()
theme.name = 'mkdocs2'
theme.dist.name = 'mkdocs2'
theme.load.side_effect = ImportError()

mock_iter.return_value = [theme]

with self.assertRaises(ImportError):
utils.get_theme_dir(theme.name)

@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_themes_warning(self, mock_iter):
theme1 = mock.Mock()
theme1.name = 'mkdocs2'
theme1.dist.name = 'mkdocs2'
theme1.load().__file__ = "some/path1"

theme2 = mock.Mock()
theme2.name = 'mkdocs2'
theme2.dist.name = 'mkdocs3'
theme2.load().__file__ = "some/path2"

mock_iter.return_value = [theme1, theme2]

with self.assertLogs('mkdocs') as cm:
theme_names = utils.get_theme_names()
self.assertEqual(
'\n'.join(cm.output),
"WARNING:mkdocs.utils:A theme named 'mkdocs2' is provided by the Python "
"packages 'mkdocs3' and 'mkdocs2'. The one in 'mkdocs3' will be used.",
)
self.assertCountEqual(theme_names, ['mkdocs2'])

@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_themes_error(self, mock_iter):
theme1 = mock.Mock()
theme1.name = 'mkdocs'
theme1.dist.name = 'mkdocs'
theme1.load().__file__ = "some/path1"

theme2 = mock.Mock()
theme2.name = 'mkdocs'
theme2.dist.name = 'mkdocs2'
theme2.load().__file__ = "some/path2"

mock_iter.return_value = [theme1, theme2]

with self.assertRaisesRegex(
exceptions.ConfigurationError,
"The theme 'mkdocs' is a builtin theme but the package 'mkdocs2' "
"attempts to provide a theme with the same name.",
):
utils.get_theme_names()

def test_nest_paths(self, j=posixpath.join):
result = utils.nest_paths(
[
Expand Down Expand Up @@ -528,6 +450,89 @@ def test_no_meta_data(self):
self.assertEqual(meta.get_data(doc), (doc, {}))


class ThemeUtilsTests(unittest.TestCase):
def setUp(self):
utils.get_themes.cache_clear()

def test_get_themes(self):
themes = utils.get_theme_names()
self.assertIn('mkdocs', themes)
self.assertIn('readthedocs', themes)

@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_theme_dir(self, mock_iter):
path = 'some/path'

theme = mock.Mock()
theme.name = 'mkdocs2'
theme.dist.name = 'mkdocs2'
theme.load().__file__ = os.path.join(path, '__init__.py')

mock_iter.return_value = [theme]

self.assertEqual(utils.get_theme_dir(theme.name), os.path.abspath(path))

def test_get_theme_dir_error(self):
with self.assertRaises(KeyError):
utils.get_theme_dir('nonexistanttheme')

@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_theme_dir_importerror(self, mock_iter):
theme = mock.Mock()
theme.name = 'mkdocs2'
theme.dist.name = 'mkdocs2'
theme.load.side_effect = ImportError()

mock_iter.return_value = [theme]

with self.assertRaises(ImportError):
utils.get_theme_dir(theme.name)

@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_themes_warning(self, mock_iter):
theme1 = mock.Mock()
theme1.name = 'mkdocs2'
theme1.dist.name = 'mkdocs2'
theme1.load().__file__ = "some/path1"

theme2 = mock.Mock()
theme2.name = 'mkdocs2'
theme2.dist.name = 'mkdocs3'
theme2.load().__file__ = "some/path2"

mock_iter.return_value = [theme1, theme2]

with self.assertLogs('mkdocs') as cm:
theme_names = utils.get_theme_names()
self.assertEqual(
'\n'.join(cm.output),
"WARNING:mkdocs.utils:A theme named 'mkdocs2' is provided by the Python "
"packages 'mkdocs3' and 'mkdocs2'. The one in 'mkdocs3' will be used.",
)
self.assertCountEqual(theme_names, ['mkdocs2'])

@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_themes_error(self, mock_iter):
theme1 = mock.Mock()
theme1.name = 'mkdocs'
theme1.dist.name = 'mkdocs'
theme1.load().__file__ = "some/path1"

theme2 = mock.Mock()
theme2.name = 'mkdocs'
theme2.dist.name = 'mkdocs2'
theme2.load().__file__ = "some/path2"

mock_iter.return_value = [theme1, theme2]

with self.assertRaisesRegex(
exceptions.ConfigurationError,
"The theme 'mkdocs' is a builtin theme but the package 'mkdocs2' "
"attempts to provide a theme with the same name.",
):
utils.get_theme_names()


class LogCounterTests(unittest.TestCase):
def setUp(self):
self.log = logging.getLogger('dummy')
Expand Down
9 changes: 8 additions & 1 deletion mkdocs/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
from typing import Any, Collection, MutableMapping

import jinja2
import yaml

try:
from yaml import CSafeLoader as SafeLoader
except ImportError: # pragma: no cover
from yaml import SafeLoader # type: ignore

from mkdocs import localization, utils
from mkdocs.config.base import ValidationError
Expand Down Expand Up @@ -118,12 +124,13 @@ def __iter__(self):
def _load_theme_config(self, name: str) -> None:
"""Recursively load theme and any parent themes."""
theme_dir = utils.get_theme_dir(name)
utils.get_themes.cache_clear()
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)
theme_config = yaml.load(f, SafeLoader)
except OSError as e:
log.debug(e)
raise ValidationError(
Expand Down
1 change: 1 addition & 0 deletions mkdocs/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ def get_theme_dir(name: str) -> str:
return os.path.dirname(os.path.abspath(theme.load().__file__))


@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] = {}
Expand Down