pax_global_header00006660000000000000000000000064145267161330014522gustar00rootroot0000000000000052 comment=3d0dd8e3b51197c3f871559c342164c83aa4081a get-deps-0.2.0/000077500000000000000000000000001452671613300132315ustar00rootroot00000000000000get-deps-0.2.0/.github/000077500000000000000000000000001452671613300145715ustar00rootroot00000000000000get-deps-0.2.0/.github/FUNDING.yml000066400000000000000000000000201452671613300163760ustar00rootroot00000000000000github: oprypin get-deps-0.2.0/.github/workflows/000077500000000000000000000000001452671613300166265ustar00rootroot00000000000000get-deps-0.2.0/.github/workflows/ci.yml000066400000000000000000000036311452671613300177470ustar00rootroot00000000000000name: CI on: push: pull_request: schedule: - cron: '0 6 * * 6' defaults: run: shell: bash jobs: test: strategy: fail-fast: false matrix: include: - python: '^3.12' os: ubuntu-latest - python: '3.12' os: macos-latest - python: '3.11' os: windows-latest - python: '3.10' os: ubuntu-latest - python: '3.9' os: macos-latest - python: '3.8' os: windows-latest - python: '3.8' os: ubuntu-latest versions: minimal runs-on: ${{matrix.os}} steps: - name: Download source uses: actions/checkout@v4 - name: Install Python uses: actions/setup-python@v4 with: python-version: ${{matrix.python}} - name: Pin to lowest versions if: matrix.versions == 'minimal' run: | sed -i -E 's/#min //; s/\b >=([0-9])/ ==\1/' pyproject.toml - name: Install Hatch run: | pip install hatch - name: Install dependencies run: | hatch run test:pip freeze - name: Run tests run: | hatch run test:test style: runs-on: ubuntu-latest steps: - name: Download source uses: actions/checkout@v4 - name: Install Python uses: actions/setup-python@v4 with: python-version: '3.12' - name: Install Hatch run: | pip install hatch - name: Install dependencies run: | hatch run style:pip freeze hatch run types:pip freeze - name: Check style if: always() run: | hatch run style:fix - name: Check formatting if: always() run: | git diff --color --exit-code - name: Check types if: always() run: | hatch run types:check get-deps-0.2.0/.github/workflows/deploy-release.yml000066400000000000000000000007651452671613300222730ustar00rootroot00000000000000name: Deploy release on: push: tags: - '*' jobs: pypi: permissions: id-token: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: python-version: '3.12' - name: Install dependencies run: pip install -U build - name: Build package run: python -m build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 get-deps-0.2.0/.gitignore000066400000000000000000000001371452671613300152220ustar00rootroot00000000000000/dist/ site*/ .mypy_cache/ .pytest_cache/ __pycache__/ *.egg-info/ .venv/ poetry.lock .vscode/ get-deps-0.2.0/.tools/000077500000000000000000000000001452671613300144475ustar00rootroot00000000000000get-deps-0.2.0/.tools/release.sh000077500000000000000000000003741452671613300164320ustar00rootroot00000000000000#!/bin/bash set -e -u -x cd "$(dirname "$0")/.." git diff --staged --quiet git diff --quiet HEAD pyproject.toml rm -rf dist hatch version "$1" hatch build git add */__init__.py git commit -m "v$1" git tag -a -m "" "v$1" git push origin master --tags get-deps-0.2.0/LICENSE.md000066400000000000000000000020731452671613300146370ustar00rootroot00000000000000MIT License Copyright (c) 2023 Oleh Prypin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. get-deps-0.2.0/README.md000066400000000000000000000052071452671613300145140ustar00rootroot00000000000000# mkdocs-get-deps **An extra command for [MkDocs][] that infers required PyPI packages from `plugins` in mkdocs.yml.** [![PyPI](https://img.shields.io/pypi/v/mkdocs-get-deps)](https://pypi.org/project/mkdocs-get-deps/) [![GitHub](https://img.shields.io/github/license/mkdocs/get-deps)](https://github.com/mkdocs/get-deps/blob/master/LICENSE.md) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/mkdocs/get-deps/ci.yml.svg)](https://github.com/mkdocs/get-deps/actions?query=event%3Apush+branch%3Amaster)
Installation:Alternatively through MkDocs itself:
```bash pip install mkdocs-get-deps ``` ```bash pip install mkdocs ```
This command guesses the Python dependencies that a MkDocs site requires in order to build. It simply prints the PyPI packages that need to be installed. In the terminal it can be combined directly with a `pip install` command, as per the last example below:
Usage:Alternatively through MkDocs itself:
```bash # Print dependencies of the current project mkdocs-get-deps # Save them into a file mkdocs-get-deps > requirements.txt # Install dependencies on the fly pip install $(mkdocs-get-deps) ``` ```bash mkdocs get-deps mkdocs get-deps > requirements.txt pip install -r requirements.txt pip install $(mkdocs get-deps) ```
The idea is that right after running this command, you can directly follow it up with `mkdocs build` and it will almost always "just work", without needing to think which dependencies to install. The way it works is by scanning [`mkdocs.yml`] for `themes:`, `plugins:`, `markdown_extensions:` items and doing a reverse lookup based on a large list of known projects (catalog, see below). Of course, you're encouraged to use a "virtualenv" with such a command. Also note that for environments that require stability (for example CI) directly installing deps in this way is not a very reliable approach as it precludes dependency pinning. The command allows overriding which config file is used (instead of `mkdocs.yml` in the current directory) as well as which catalog of projects is used (instead of downloading it from the default location). See [`mkdocs get-deps --help`](https://www.mkdocs.org/user-guide/cli/#mkdocs-get-deps). ## MkDocs' official catalog of plugins Check out and add all your general-purpose plugins, themes and extensions there, so that they can be looked up through `mkdocs get-deps`. [MkDocs]: https://www.mkdocs.org/ [`mkdocs.yml`]: https://www.mkdocs.org/user-guide/configuration/ get-deps-0.2.0/mkdocs_get_deps/000077500000000000000000000000001452671613300163635ustar00rootroot00000000000000get-deps-0.2.0/mkdocs_get_deps/__init__.py000066400000000000000000000164321452671613300205020ustar00rootroot00000000000000from __future__ import annotations __version__ = "0.2.0" import dataclasses import datetime import functools import io import logging import os import sys import urllib.parse from typing import IO, Any, BinaryIO, Collection, Mapping, Sequence import yaml from . import cache, yaml_util log = logging.getLogger(f"mkdocs.{__name__}") DEFAULT_PROJECTS_FILE = "https://raw.githubusercontent.com/mkdocs/catalog/main/projects.yaml" BUILTIN_THEMES = {"mkdocs", "readthedocs"} BUILTIN_PLUGINS = {"search"} _BUILTIN_EXTENSIONS = "abbr admonition attr_list codehilite def_list extra fenced_code footnotes md_in_html meta nl2br sane_lists smarty tables toc wikilinks legacy_attrs legacy_em".split() BUILTIN_EXTENSIONS = { *_BUILTIN_EXTENSIONS, *(f"markdown.extensions.{e}" for e in _BUILTIN_EXTENSIONS), } _NotFound = () def _dig(cfg, keys: str): """ Receives a string such as 'foo.bar' and returns `cfg['foo']['bar']`, or `_NotFound`. A list of single-item dicts gets converted to a flat dict. This is intended for `plugins` config. """ key, _, rest = keys.partition(".") try: cfg = cfg[key] except (KeyError, TypeError): return _NotFound if isinstance(cfg, list): orig_cfg = cfg cfg = {} for item in reversed(orig_cfg): if isinstance(item, dict) and len(item) == 1: cfg.update(item) elif isinstance(item, str): cfg[item] = {} if not rest: return cfg return _dig(cfg, rest) def _strings(obj) -> Sequence[str]: if isinstance(obj, str): return (obj,) else: return tuple(obj) @functools.lru_cache(maxsize=None) def _entry_points(group: str) -> Mapping[str, Any]: if sys.version_info >= (3, 10): from importlib.metadata import entry_points else: from importlib_metadata import entry_points eps = {ep.name: ep for ep in entry_points(group=group)} log.debug(f"Available '{group}' entry points: {sorted(eps)}") return eps @dataclasses.dataclass(frozen=True) class _PluginKind: projects_key: str entry_points_key: str def __str__(self) -> str: return self.projects_key.rpartition("_")[-1] def get_projects_file(path: str | None = None) -> BinaryIO: if path is None: path = DEFAULT_PROJECTS_FILE if urllib.parse.urlsplit(path).scheme in ("http", "https"): content = cache.download_and_cache_url(path, datetime.timedelta(days=1)) else: with open(path, "rb") as f: content = f.read() return io.BytesIO(content) def get_deps( config_file: IO | os.PathLike | str | None = None, projects_file: IO | None = None, ) -> Collection[str]: """ Print PyPI package dependencies inferred from a mkdocs.yml file based on a reverse mapping of known projects. Args: config_file: Non-default mkdocs.yml file - content as a buffer, or path. projects_file: File/buffer that declares all known MkDocs-related projects. The file is in YAML format and contains `projects: [{mkdocs_theme:, mkdocs_plugin:, markdown_extension:}] """ if config_file is None: if os.path.isfile("mkdocs.yml"): config_file = "mkdocs.yml" elif os.path.isfile("mkdocs.yaml"): config_file = "mkdocs.yaml" else: config_file = "mkdocs.yml" opened_config_file: IO if isinstance(config_file, (str, os.PathLike)): config_file = os.path.abspath(config_file) opened_config_file = open(config_file, "rb") else: opened_config_file = config_file log.debug(f"Loading configuration file: {config_file}") with opened_config_file: cfg = yaml_util.yaml_load(opened_config_file) if not isinstance(cfg, dict): raise ValueError( f"The configuration is invalid. Expected a key-value mapping but received {type(cfg)}" ) packages_to_install = set() if all(c not in cfg for c in ("site_name", "theme", "plugins", "markdown_extensions")): log.warning(f"The file {config_file!r} doesn't seem to be a mkdocs.yml config file") else: if _dig(cfg, "theme.locale") not in (_NotFound, "en"): packages_to_install.add("mkdocs[i18n]") else: packages_to_install.add("mkdocs") try: theme = cfg["theme"]["name"] except (KeyError, TypeError): theme = cfg.get("theme") themes = {theme} if theme else set() plugins = set(_strings(_dig(cfg, "plugins"))) extensions = set(_strings(_dig(cfg, "markdown_extensions"))) wanted_plugins = ( (_PluginKind("mkdocs_theme", "mkdocs.themes"), themes - BUILTIN_THEMES), (_PluginKind("mkdocs_plugin", "mkdocs.plugins"), plugins - BUILTIN_PLUGINS), (_PluginKind("markdown_extension", "markdown.extensions"), extensions - BUILTIN_EXTENSIONS), ) for kind, wanted in wanted_plugins: log.debug(f"Wanted {kind}s: {sorted(wanted)}") if projects_file is None: projects_file = get_projects_file() with projects_file: projects = yaml.load(projects_file, Loader=yaml_util.SafeLoader)["projects"] for project in projects: for kind, wanted in wanted_plugins: available = _strings(project.get(kind.projects_key, ())) for entry_name in available: if ( # Also check theme-namespaced plugin names against the current theme. "/" in entry_name and theme is not None and kind.projects_key == "mkdocs_plugin" and entry_name.startswith(f"{theme}/") and entry_name[len(theme) + 1 :] in wanted and entry_name not in wanted ): entry_name = entry_name[len(theme) + 1 :] if entry_name in wanted: if "pypi_id" in project: install_name = project["pypi_id"] elif "github_id" in project: install_name = "git+https://github.com/{github_id}".format_map(project) else: log.error( f"Can't find how to install {kind} '{entry_name}' although it was identified as {project}" ) continue packages_to_install.add(install_name) for extra_key, extra_pkgs in project.get("extra_dependencies", {}).items(): if _dig(cfg, extra_key) is not _NotFound: packages_to_install.update(_strings(extra_pkgs)) wanted.remove(entry_name) for kind, wanted in wanted_plugins: for entry_name in sorted(wanted): dist_name = None ep = _entry_points(kind.entry_points_key).get(entry_name) if ep is not None and ep.dist is not None: dist_name = ep.dist.name warning = ( f"{str(kind).capitalize()} '{entry_name}' is not provided by any registered project" ) if ep is not None: warning += " but is installed locally" if dist_name: warning += f" from '{dist_name}'" log.info(warning) else: log.warning(warning) return sorted(packages_to_install) get-deps-0.2.0/mkdocs_get_deps/__main__.py000066400000000000000000000031231452671613300204540ustar00rootroot00000000000000from __future__ import annotations import argparse import logging import sys from . import get_deps, get_projects_file parser = argparse.ArgumentParser( description="Show required PyPI packages inferred from plugins in mkdocs.yml." ) parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output") parser.add_argument( "-f", "--config-file", type=argparse.FileType("r"), help="Provide a specific MkDocs config. This can be a file name, or '-' to read from stdin.", default=None, ) parser.add_argument( "-p", "--projects-file", help="URL or local path of the registry file that declares all known MkDocs-related projects.", default="https://raw.githubusercontent.com/mkdocs/catalog/main/projects.yaml", ) class CountHandler(logging.NullHandler): warning_count = 0 def handle(self, record): rv = self.filter(record) if rv: self.warning_count += 1 return rv def cli(): args = parser.parse_args() logging.basicConfig( level=logging.DEBUG if args.verbose else logging.INFO, format="%(levelname)s - mkdocs-get-deps: %(message)s", ) warning_counter = CountHandler() warning_counter.setLevel(logging.WARNING) logging.getLogger("mkdocs").addHandler(warning_counter) with get_projects_file(args.projects_file) as projects_file: deps = get_deps(config_file=args.config_file, projects_file=projects_file) for dep in deps: print(dep) # noqa: T201 if warning_counter.warning_count: sys.exit(1) if __name__ == "__main__": cli() get-deps-0.2.0/mkdocs_get_deps/cache.py000066400000000000000000000046431452671613300200070ustar00rootroot00000000000000from __future__ import annotations import datetime import hashlib import logging import os import random import urllib.request from typing import Callable import platformdirs from . import __version__ log = logging.getLogger(f"mkdocs.{__name__}") def _download_url(url: str) -> bytes: req = urllib.request.Request(url, headers={"User-Agent": f"mkdocs-get-deps/{__version__}"}) with urllib.request.urlopen(req) as resp: return resp.read() def download_and_cache_url( url: str, cache_duration: datetime.timedelta, *, download: Callable[[str], bytes] = _download_url, comment: bytes = b"# ", ) -> bytes: """Downloads a file from the URL, stores it under ~/.cache/, and returns its content. For tracking the age of the content, a prefix is inserted into the stored file, rather than relying on mtime. Args: url: URL to use. download: Callback that will accept the URL and actually perform the download. cache_duration: How long to consider the URL content cached. comment: The appropriate comment prefix for this file format. """ directory = os.path.join(platformdirs.user_cache_dir("mkdocs"), "mkdocs_url_cache") name_hash = hashlib.sha256(url.encode()).hexdigest()[:32] path = os.path.join(directory, name_hash + os.path.splitext(url)[1]) now = int(datetime.datetime.now(datetime.timezone.utc).timestamp()) prefix = b"%s%s downloaded at timestamp " % (comment, url.encode()) # Check for cached file and try to return it if os.path.isfile(path): try: with open(path, "rb") as f: line = f.readline() if line.startswith(prefix): line = line[len(prefix) :] timestamp = int(line) if datetime.timedelta(seconds=(now - timestamp)) <= cache_duration: log.debug(f"Using cached '{path}' for '{url}'") return f.read() except (OSError, ValueError) as e: log.debug(f"{type(e).__name__}: {e}") # Download and cache the file log.debug(f"Downloading '{url}' to '{path}'") content = download(url) os.makedirs(directory, exist_ok=True) temp_filename = f"{path}.{random.randrange(1 << 32):08x}.part" with open(temp_filename, "wb") as f: f.write(b"%s%d\n" % (prefix, now)) f.write(content) os.replace(temp_filename, path) return content get-deps-0.2.0/mkdocs_get_deps/py.typed000066400000000000000000000000001452671613300200500ustar00rootroot00000000000000get-deps-0.2.0/mkdocs_get_deps/yaml_util.py000066400000000000000000000030201452671613300207270ustar00rootroot00000000000000from __future__ import annotations import logging import os import os.path from typing import IO, Any import mergedeep # type: ignore import yaml try: from yaml import CSafeLoader as SafeLoader except ImportError: from yaml import SafeLoader # type: ignore log = logging.getLogger(f"mkdocs.{__name__}") class YamlLoader(SafeLoader): pass # Prevent errors from trying to access external modules which may not be installed yet. YamlLoader.add_constructor("!ENV", lambda loader, node: None) # type: ignore YamlLoader.add_constructor("!relative", lambda loader, node: None) # type: ignore YamlLoader.add_multi_constructor( "tag:yaml.org,2002:python/name:", lambda loader, suffix, node: None ) YamlLoader.add_multi_constructor( "tag:yaml.org,2002:python/object/apply:", lambda loader, suffix, node: None ) def yaml_load(source: IO | str) -> dict[str, Any]: """Return dict of source YAML file using loader, recursively deep merging inherited parent.""" result = yaml.load(source, Loader=YamlLoader) if result is None: return {} if ( "INHERIT" in result and not isinstance(source, str) and getattr(source, "name", None) is not None ): relpath = result.pop("INHERIT") abspath = os.path.normpath(os.path.join(os.path.dirname(source.name), relpath)) log.debug(f"Loading inherited configuration file: {abspath}") with open(abspath, "rb") as f: parent = yaml_load(f) result = mergedeep.merge(parent, result) return result get-deps-0.2.0/pyproject.toml000066400000000000000000000062401452671613300161470ustar00rootroot00000000000000[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "mkdocs-get-deps" description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" readme = "README.md" license = "MIT" keywords = ["mkdocs"] authors = [ {name = "Oleh Prypin", email = "oleh@pryp.in"}, ] classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Environment :: Web Environment", "Intended Audience :: Information Technology", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Documentation", "Typing :: Typed", ] dynamic = ["version"] requires-python = ">=3.8" dependencies = [ "importlib-metadata >=4.3; python_version < '3.10'", "mergedeep >=1.3.4", "platformdirs >=2.2.0", "PyYAML >=5.1", ] [project.urls] Source = "https://github.com/mkdocs/get-deps" Issues = "https://github.com/mkdocs/get-deps/issues" History = "https://github.com/mkdocs/get-deps/releases" [project.scripts] mkdocs-get-deps = "mkdocs_get_deps.__main__:cli" [tool.hatch.version] path = "mkdocs_get_deps/__init__.py" [tool.hatch.build.targets.sdist] include = ["/mkdocs_get_deps", "/tests"] [tool.hatch.envs.default.scripts] all = [ "hatch run style:fix", "hatch run types:check", "hatch run test:test", ] [tool.hatch.envs.test] dependencies = [ "pytest", ] [tool.hatch.envs.test.scripts] test = [ "pytest -q", ] [tool.hatch.envs.types] dependencies = [ "mypy", "types-PyYAML", ] [tool.hatch.envs.types.scripts] check = [ "mypy mkdocs_get_deps" ] [tool.hatch.envs.style] skip-install = true dependencies = [ "ruff", ] [tool.hatch.envs.style.scripts] fix = [ "ruff check --fix mkdocs_get_deps tests", "format", ] format = [ "ruff format -q mkdocs_get_deps tests", ] [tool.ruff] line-length = 100 select = [ "I", "F", "W", "E", "UP", "YTT", "C4", "DTZ", "FA", "ISC", "PIE", "T20", "RSE", "TCH", "B002", "B003", "B005", "B007", "B009", "B012", "B013", "B014", "B015", "B018", "B020", "B021", "B023", "B026", "B033", "B034", "B905", "COM818", "PERF101", "PGH002", "PGH004", "PGH005", "FLY002", "PLC", "PLE", "PLR0124", "PLR0133", "PLR0206", "PLR0402", "PLR1701", "PLR1722", "PLW0120", "PLW0127", "PLW0129", "PLW0131", "PLW0406", "PLW0602", "PLW0603", "PLW0711", "RUF001", "RUF005", "RUF007", "RUF010", "RUF013", "RUF100", "RUF200", "SIM101", "SIM107", "SIM201", "SIM202", "SIM208", "SIM210", "SIM211", "SIM300", "SIM401", "SIM910", ] ignore = ["E501", "E731"] [tool.ruff.flake8-comprehensions] allow-dict-calls-with-keyword-arguments = true [tool.mypy] warn_unreachable = true allow_redefinition = true [tool.pytest.ini_options] addopts = "--tb=native" enable_assertion_pass_hook = true filterwarnings = ["ignore::DeprecationWarning:.*:", "default::DeprecationWarning:mkdocs_get_deps.*:"] testpaths = ["tests"] get-deps-0.2.0/setup.py000066400000000000000000000004121452671613300147400ustar00rootroot00000000000000"""Installation using setup.py is not supported. Use `pip install .` instead.""" import sys from setuptools import setup sys.exit(__doc__) # Fake reference so GitHub still considers it a real package for statistics purposes. setup( name="mkdocs-get-deps", ) get-deps-0.2.0/tests/000077500000000000000000000000001452671613300143735ustar00rootroot00000000000000get-deps-0.2.0/tests/example-projects.yaml000066400000000000000000000061121452671613300205410ustar00rootroot00000000000000# DO NOT UPDATE THIS FILE, only for tests. # This is an intentionally small subset of https://github.com/mkdocs/catalog projects: - name: Material for MkDocs mkdocs_theme: material mkdocs_plugin: [material/info, material/offline, material/search, material/social, material/tags] github_id: squidfunk/mkdocs-material pypi_id: mkdocs-material - name: Bootstrap4 mkdocs_theme: bootstrap4 github_id: byrnereese/mkdocs-bootstrap4 pypi_id: mkdocs-bootstrap4 - name: Bootstrap 4 mkdocs_theme: bootstrap4 shadowed: [mkdocs_theme] github_id: LukeCarrier/mkdocs-theme-bootstrap4 pypi_id: mkdocs-theme-bootstrap4 - name: Bootswatch mkdocs_theme: [cerulean, cosmo, cyborg, darkly, flatly, journal, litera, lumen, lux, materia, minty, pulse, sandstone, simplex, slate, solar, spacelab, superhero, united, yeti] github_id: mkdocs/mkdocs-bootswatch pypi_id: mkdocs-bootswatch - name: mkdocstrings mkdocs_plugin: mkdocstrings extra_dependencies: plugins.mkdocstrings.handlers.crystal: mkdocstrings-crystal plugins.mkdocstrings.handlers.python: mkdocstrings-python github_id: mkdocstrings/mkdocstrings pypi_id: mkdocstrings - name: mkdocs-click markdown_extension: mkdocs-click github_id: DataDog/mkdocs-click pypi_id: mkdocs-click - name: blog mkdocs_plugin: blog github_id: andyoakley/mkdocs-blog - name: Blogs for MkDocs shadowed: [mkdocs_plugin] mkdocs_plugin: blog github_id: fmaida/mkdocs-blog-plugin - name: foo homepage: foo gitlab_id: bar/foo - name: Termage mkdocs_plugin: termage github_id: bczsalba/Termage - name: Github-Links markdown_extension: mdx_gh_links github_id: Python-Markdown/github-links pypi_id: mdx-gh-links - name: autorefs mkdocs_plugin: autorefs github_id: mkdocstrings/autorefs pypi_id: mkdocs-autorefs - name: mkdocs-redirects mkdocs_plugin: redirects github_id: mkdocs/mkdocs-redirects pypi_id: mkdocs-redirects - name: markdown-callouts markdown_extension: callouts github_id: oprypin/markdown-callouts pypi_id: markdown-callouts - name: PyMdown Extensions markdown_extension: [pymdownx.arithmatex, pymdownx.b64, pymdownx.betterem, pymdownx.caret, pymdownx.critic, pymdownx.details, pymdownx.emoji, pymdownx.escapeall, pymdownx.extra, pymdownx.highlight, pymdownx.inlinehilite, pymdownx.keys, pymdownx.magiclink, pymdownx.mark, pymdownx.pathconverter, pymdownx.progressbar, pymdownx.saneheaders, pymdownx.smartsymbols, pymdownx.snippets, pymdownx.striphtml, pymdownx.superfences, pymdownx.tabbed, pymdownx.tasklist, pymdownx.tilde] github_id: facelessuser/pymdown-extensions pypi_id: pymdown-extensions - name: literate-nav mkdocs_plugin: literate-nav github_id: oprypin/mkdocs-literate-nav pypi_id: mkdocs-literate-nav - name: mkdocs-code-validator mkdocs_plugin: code-validator github_id: oprypin/mkdocs-code-validator pypi_id: mkdocs-code-validator - name: tags mkdocs_plugin: tags description: Processes tags in yaml metadata github_id: jldiaz/mkdocs-plugin-tags pypi_id: mkdocs-plugin-tags - name: tags mkdocs_plugin: autotag github_id: six-two/mkdocs-auto-tag-plugin pypi_id: mkdocs-auto-tag-plugin get-deps-0.2.0/tests/test_get_deps.py000066400000000000000000000122521452671613300176000ustar00rootroot00000000000000import contextlib import os import tempfile import textwrap import unittest from mkdocs_get_deps import get_deps _projects_file_path = os.path.join( os.path.abspath(os.path.dirname(__file__)), "example-projects.yaml" ) class TestGetDeps(unittest.TestCase): @contextlib.contextmanager def _assert_logs(self, expected): with self.assertLogs("mkdocs.mkdocs_get_deps") as cm: yield msgs = [f"{r.levelname}:{r.message}" for r in cm.records] self.assertRegex("\n".join(msgs), textwrap.dedent(expected).strip("\n")) def _test_get_deps(self, yml, expected): if yml: yml = "site_name: Test\n" + textwrap.dedent(yml) with tempfile.TemporaryDirectory() as tempdir: mkdocs_yml = os.path.join(tempdir, "mkdocs.yml") with open(mkdocs_yml, "w", encoding="utf-8") as f: f.write(yml) with open(_projects_file_path, encoding="utf-8") as projects_file: actual = get_deps(config_file=mkdocs_yml, projects_file=projects_file) self.assertEqual(expected, actual) def test_empty_config(self): expected_logs = "WARNING:The file '.+' doesn't seem to be a mkdocs.yml config file" with self._assert_logs(expected_logs): self._test_get_deps("", []) def test_just_search(self): cfg = """ plugins: [search] """ self._test_get_deps(cfg, ["mkdocs"]) def test_mkdocs_config(self): cfg = """ site_name: MkDocs theme: name: mkdocs locale: en markdown_extensions: - toc: permalink:  - attr_list - def_list - tables - pymdownx.highlight: use_pygments: false - pymdownx.snippets - pymdownx.superfences - callouts - mdx_gh_links: user: mkdocs repo: mkdocs - mkdocs-click plugins: - search - redirects: - autorefs - literate-nav: nav_file: README.md implicit_index: true - mkdocstrings: handlers: python: options: docstring_section_style: list """ self._test_get_deps( cfg, [ "markdown-callouts", "mdx-gh-links", "mkdocs", "mkdocs-autorefs", "mkdocs-click", "mkdocs-literate-nav", "mkdocs-redirects", "mkdocstrings", "mkdocstrings-python", "pymdown-extensions", ], ) def test_dict_keys_and_ignores_env(self): cfg = """ theme: name: material plugins: code-validator: enabled: !ENV [LINT, false] markdown_extensions: pymdownx.emoji: emoji_index: !!python/name:materialx.emoji.twemoji emoji_generator: !!python/name:materialx.emoji.to_svg """ self._test_get_deps( cfg, ["mkdocs", "mkdocs-code-validator", "mkdocs-material", "pymdown-extensions"] ) def test_theme_precedence(self): cfg = """ plugins: - tags theme: material """ self._test_get_deps(cfg, ["mkdocs", "mkdocs-material"]) cfg = """ plugins: - material/tags """ self._test_get_deps(cfg, ["mkdocs", "mkdocs-material"]) cfg = """ plugins: - tags """ self._test_get_deps(cfg, ["mkdocs", "mkdocs-plugin-tags"]) def test_nonexistent(self): cfg = """ plugins: - taglttghhmdu - syyisjupkbpo - redirects theme: qndyakplooyh markdown_extensions: - saqdhyndpvpa """ expected_logs = """ WARNING:Theme 'qndyakplooyh' is not provided by any registered project WARNING:Plugin 'syyisjupkbpo' is not provided by any registered project WARNING:Plugin 'taglttghhmdu' is not provided by any registered project WARNING:Extension 'saqdhyndpvpa' is not provided by any registered project """ with self._assert_logs(expected_logs): self._test_get_deps(cfg, ["mkdocs", "mkdocs-redirects"]) def test_git_and_shadowed(self): cfg = """ theme: bootstrap4 plugins: [blog] """ self._test_get_deps( cfg, ["git+https://github.com/andyoakley/mkdocs-blog", "mkdocs", "mkdocs-bootstrap4"] ) def test_multi_theme(self): cfg = """ theme: minty """ self._test_get_deps(cfg, ["mkdocs", "mkdocs-bootswatch"]) def test_with_locale(self): cfg = """ theme: name: mkdocs locale: uk """ self._test_get_deps(cfg, ["mkdocs[i18n]"])