From 5802a5d2b775848b9c209811a93c6317c5dd1b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Wielu=C5=84ski?= Date: Fri, 22 Dec 2023 13:35:39 +0100 Subject: [PATCH] improvements to target flag handling --- README.md | 2 +- d2/__init__.py | 4 ++-- d2/config.py | 23 ++++++++++----------- d2/fence.py | 4 ++-- d2/img.py | 2 +- d2/plugin.py | 35 ++++++++++++++++++++++---------- docs/docs/guide/configuration.md | 29 ++++++++++++++++++++++++++ docs/docs/index.md | 5 ----- setup.py | 9 ++++++-- 9 files changed, 77 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 9fa7e28..2bdf763 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A plugin for embedding D2 diagrams in MkDocs. ## Requirements * [MkDocs](https://www.mkdocs.org/) >= 1.5.0 -* [D2](https://d2lang.com) +* [D2](https://d2lang.com) >= 0.6.3 ## Installation diff --git a/d2/__init__.py b/d2/__init__.py index 934dede..df5b1ca 100644 --- a/d2/__init__.py +++ b/d2/__init__.py @@ -1,5 +1,5 @@ from functools import partial -from typing import Callable, Dict, Tuple +from typing import Callable, List, Tuple from mkdocs.plugins import log @@ -9,4 +9,4 @@ warning = partial(log.warning, f"{NAME}: %s") error = partial(log.error, f"{NAME}: %s") -Renderer = Callable[[bytes, Dict[str, str]], Tuple[str, bool]] +Renderer = Callable[[bytes, List[str]], Tuple[str, bool]] diff --git a/d2/config.py b/d2/config.py index f8a0f7d..b18f2e1 100644 --- a/d2/config.py +++ b/d2/config.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import List from mkdocs.config import config_options from mkdocs.config.base import Config @@ -17,7 +17,7 @@ class PluginConfig(Config): pad = config_options.Type(int, default=100) scale = config_options.Type(float, default=-1.0) force_appendix = config_options.Type(bool, default=False) - target = config_options.Type(str, default="\'\'") + target = config_options.Type(str, default="''") def d2_config(self): _dict = {} @@ -38,17 +38,16 @@ class D2Config(BaseModel, extra="forbid"): force_appendix: bool target: str - def env(self) -> list[str]: + def opts(self) -> List[str]: opts = [ - "--layout", self.layout, - "--theme", str(self.theme), - "--dark-theme", str(self.dark_theme), - "--pad", str(self.pad), - "--scale", str(self.scale), - "--target", str(self.target) + f"--layout={self.layout}", + f"--theme={self.theme}", + f"--dark-theme={self.dark_theme}", + f"--sketch={str(self.sketch).lower()}", + f"--pad={self.pad}", + f"--scale={self.scale}", + f"--force-appendix={str(self.force_appendix).lower()}", + f"--target={self.target}", ] - opts = opts + [ "--sketch" ] if self.sketch else opts - opts = opts + [ "--force-appendix" ] if self.force_appendix else opts - return opts diff --git a/d2/fence.py b/d2/fence.py index 75dde57..cc8276d 100644 --- a/d2/fence.py +++ b/d2/fence.py @@ -35,7 +35,7 @@ def validator( error(e) return False - options["env"] = cfg.env() + options["opts"] = cfg.opts() return True def formatter( @@ -52,7 +52,7 @@ def formatter( source, language, class_name, options, md, **kwargs ) - result, ok = self.renderer(source.encode(), options["env"]) + result, ok = self.renderer(source.encode(), options["opts"]) if not ok: error(result) return fence_code_format( diff --git a/d2/img.py b/d2/img.py index 35b43fe..fc189d9 100644 --- a/d2/img.py +++ b/d2/img.py @@ -50,7 +50,7 @@ def run(self, root: etree.Element) -> etree.Element | None: error(e) continue - result, ok = self.renderer(source, cfg.env()) + result, ok = self.renderer(source, cfg.opts()) if not ok: error(result) continue diff --git a/d2/plugin.py b/d2/plugin.py index da96267..8ae0c5a 100644 --- a/d2/plugin.py +++ b/d2/plugin.py @@ -1,21 +1,23 @@ import dbm import os -import shutil import subprocess from functools import partial from hashlib import sha1 from pathlib import Path -from typing import Dict, MutableMapping, Tuple +from typing import List, MutableMapping, Tuple from mkdocs.config.defaults import MkDocsConfig from mkdocs.exceptions import ConfigurationError from mkdocs.plugins import BasePlugin from mkdocs.utils.yaml import RelativeDirPlaceholder +from packaging import version from d2 import info from d2.config import PluginConfig from d2.fence import D2CustomFence +REQUIRED_VERSION = version.parse("0.6.3") + class Plugin(BasePlugin[PluginConfig]): def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None: @@ -28,8 +30,18 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None: info(f"Using cache at {path} ({backend})") cache = dbm.open(path, "c") - if shutil.which(self.config.executable) is None: + try: + result = subprocess.run( + [self.config.executable, "--version"], + capture_output=True, + ) + except FileNotFoundError: raise ConfigurationError(f"executable '{self.config.executable}' not found") + d2_version = version.parse(result.stdout.decode().strip()) + if d2_version < REQUIRED_VERSION: + raise ConfigurationError( + f"required d2 version {REQUIRED_VERSION} not satisfied, found {d2_version}" + ) renderer = partial(render, self.config.executable, cache) # type: ignore @@ -65,30 +77,31 @@ def render( executable: str, cache: MutableMapping[bytes, bytes] | None, source: bytes, - env: list[str], + opts: List[str], ) -> Tuple[str, bool]: key = "" if cache is not None: - brute_key = f"{source.hex()}.{env}" - key = sha1(brute_key.encode()).digest() + key = source.hex() + for opt in opts: + key = f"{key}.{opt}" + key = sha1(key.encode()).digest() if key in cache: return cache[key].decode(), True - - process = [ executable ] + env + [ "-", "-" ] try: result = subprocess.run( - process, + [executable, *opts, "-", "-"], input=source, capture_output=True, ) except Exception as e: return str(e), False - stdout = result.stdout.decode().strip() if result.returncode != 0: - return stdout, False + stderr = result.stderr.decode().strip() + return stderr, False + stdout = result.stdout.decode().strip() if key != "" and cache is not None: cache[key] = stdout.encode() return stdout, True diff --git a/docs/docs/guide/configuration.md b/docs/docs/guide/configuration.md index b56959a..c5e000d 100644 --- a/docs/docs/guide/configuration.md +++ b/docs/docs/guide/configuration.md @@ -19,6 +19,7 @@ plugins: pad: 100 scale: -1.0 force_appendix: False + target: "''" ``` If an option is not specified, default value (seen above) will be used. @@ -97,6 +98,34 @@ Bob -> Alice Bob -> Alice ``` +##### Rendering specific target + +````md +```d2 pad="10" scale="1" target="alternative" +scenarios: { + main: { + Bob -> Alice + } + + alternative: { + Alice -> Bob + } +} +``` +```` + +```d2 pad="10" scale="1" target="alternative" +scenarios: { + main: { + Bob -> Alice + } + + alternative: { + Alice -> Bob + } +} +``` + ### Image tags Image tags use [attr_list](https://python-markdown.github.io/extensions/attr_list/) diff --git a/docs/docs/index.md b/docs/docs/index.md index 5c628c0..5b22330 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -28,8 +28,3 @@ plugins: The plugin will automatically add `pymdownx.superfences` and `attr_list` to the list of enabled markdown extensions. - -## Known issues - -* [Layered diagrams](https://d2lang.com/tour/composition/) (animations) are not supported. - D2 does not allow outputing such diagrams to stdout. diff --git a/setup.py b/setup.py index 45a9f27..fa91a33 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="mkdocs-d2-plugin", - version="1.1.0", + version="1.2.0", description="MkDocs plugin for D2", long_description=long_description, long_description_content_type="text/markdown", @@ -18,7 +18,12 @@ author_email="michal@wielunski.net", license="MIT", python_requires=">=3.8", - install_requires=["mkdocs>=1.5.0", "pymdown-extensions>=9.0", "pydantic>=2.0"], + install_requires=[ + "mkdocs>=1.5.0", + "pymdown-extensions>=9.0", + "pydantic>=2.0", + "packaging", + ], classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers",