From 4669ca1b103a2d23b3d25abb8e231467733a2b31 Mon Sep 17 00:00:00 2001 From: David de la Iglesia Castro Date: Thu, 7 Jul 2022 12:16:59 +0200 Subject: [PATCH] Introduce `render_markdown`. Closes #62 --- setup.cfg | 5 +++ src/dvc_render/exceptions.py | 7 ++++ src/dvc_render/html.py | 5 +-- src/dvc_render/markdown.py | 73 ++++++++++++++++++++++++++++++++++++ tests/test_markdown.py | 46 +++++++++++++++++++++++ 5 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 src/dvc_render/markdown.py create mode 100644 tests/test_markdown.py diff --git a/setup.cfg b/setup.cfg index eacffba..d86bbcf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,9 @@ packages = find: [options.extras_require] table = tabulate>=0.8.7 +markdown = + %(table)s + matplotlib docs = mkdocs==1.3.0 mkdocs-gen-files==0.3.4 @@ -36,6 +39,7 @@ docs = mkdocstrings-python==0.7.0 tests = %(table)s + %(markdown)s funcy>=1.17 pytest==7.1.2 pytest-sugar==0.9.4 @@ -46,6 +50,7 @@ tests = pytest-test-utils>=0.0.6 dev = %(table)s + %(markdown)s %(tests)s %(docs)s diff --git a/src/dvc_render/exceptions.py b/src/dvc_render/exceptions.py index ea65729..cf256a6 100644 --- a/src/dvc_render/exceptions.py +++ b/src/dvc_render/exceptions.py @@ -1,2 +1,9 @@ class DvcRenderException(Exception): pass + + +class MissingPlaceholderError(DvcRenderException): + def __init__(self, placeholder, template_type): + super().__init__( + f"{template_type} template has to contain '{placeholder}'." + ) diff --git a/src/dvc_render/html.py b/src/dvc_render/html.py index 7ae1ac2..5bab070 100644 --- a/src/dvc_render/html.py +++ b/src/dvc_render/html.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Optional +from typing import TYPE_CHECKING, List, Optional from .exceptions import DvcRenderException @@ -81,7 +81,6 @@ def embed(self) -> str: def render_html( renderers: List["Renderer"], output_file: "StrPath", - metrics: Optional[Dict[str, Dict]] = None, template_path: Optional["StrPath"] = None, refresh_seconds: Optional[int] = None, ) -> "StrPath": @@ -95,8 +94,6 @@ def render_html( page_html = fobj.read() document = HTML(page_html, refresh_seconds=refresh_seconds) - if metrics: - document.with_element("
") for renderer in renderers: document.with_scripts(renderer.SCRIPTS) diff --git a/src/dvc_render/markdown.py b/src/dvc_render/markdown.py new file mode 100644 index 0000000..2f77fb4 --- /dev/null +++ b/src/dvc_render/markdown.py @@ -0,0 +1,73 @@ +from pathlib import Path +from typing import TYPE_CHECKING, List, Optional + +from .exceptions import MissingPlaceholderError + +if TYPE_CHECKING: + from .base import Renderer, StrPath + + +PAGE_MARKDOWN = """# DVC Report + +{renderers} +""" + + +class Markdown: + RENDERERS_PLACEHOLDER = "renderers" + RENDERERS_PLACEHOLDER_FORMAT_STR = f"{{{RENDERERS_PLACEHOLDER}}}" + + def __init__( + self, + template: Optional[str] = None, + ): + template = template or PAGE_MARKDOWN + if self.RENDERERS_PLACEHOLDER_FORMAT_STR not in template: + raise MissingPlaceholderError( + self.RENDERERS_PLACEHOLDER_FORMAT_STR, "Markdown" + ) + + self.template = template + self.elements: List[str] = [] + + def with_element(self, md: str) -> "Markdown": + "Adds custom markdown element." + self.elements.append(md) + return self + + def embed(self) -> str: + "Format Markdown template with all elements." + kwargs = { + self.RENDERERS_PLACEHOLDER: "\n".join(self.elements), + } + for placeholder, value in kwargs.items(): + self.template = self.template.replace( + "{" + placeholder + "}", value + ) + return self.template + + +def render_markdown( + renderers: List["Renderer"], + output_file: "StrPath", + template_path: Optional["StrPath"] = None, +) -> "StrPath": + "User renderers to fill an Markdown template and write to path." + output_path = Path(output_file) + output_path.parent.mkdir(exist_ok=True) + + page = None + if template_path: + with open(template_path, encoding="utf-8") as fobj: + page = fobj.read() + + document = Markdown(page) + + for renderer in renderers: + document.with_element( + renderer.generate_markdown(output_path=output_path) + ) + + output_path.write_text(document.embed(), encoding="utf8") + + return output_file diff --git a/tests/test_markdown.py b/tests/test_markdown.py new file mode 100644 index 0000000..8dc6e2d --- /dev/null +++ b/tests/test_markdown.py @@ -0,0 +1,46 @@ +import pytest + +from dvc_render.markdown import ( + PAGE_MARKDOWN, + Markdown, + MissingPlaceholderError, +) + +# pylint: disable=missing-function-docstring, R0801 + + +CUSTOM_PAGE_MARKDOWN = """# CUSTOM REPORT + +{renderers} +""" + + +@pytest.mark.parametrize( + "template,page_elements,expected_page", + [ + ( + None, + ["content"], + PAGE_MARKDOWN.replace("{renderers}", "content"), + ), + ( + CUSTOM_PAGE_MARKDOWN, + ["content"], + CUSTOM_PAGE_MARKDOWN.format(renderers="content"), + ), + ], +) +def test_html(template, page_elements, expected_page): + page = Markdown(template) + page.elements = page_elements + + result = page.embed() + + assert result == expected_page + + +def test_no_placeholder(): + template = "# Missing Placeholder" + + with pytest.raises(MissingPlaceholderError): + Markdown(template)