From e0c361b42220c3dd7a37121581f45061bfb77240 Mon Sep 17 00:00:00 2001
From: Michele Tessaro
Date: Tue, 4 Oct 2022 18:39:29 +0200
Subject: [PATCH 1/5] new: usr: added option to disable image maps (refs #74)
---
README.md | 1 +
plantuml_markdown.py | 24 ++++++++++++++----------
test/test_plantuml.py | 19 +++++++++++++++++++
3 files changed, 34 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index cc6b5a4..483264d 100644
--- a/README.md
+++ b/README.md
@@ -212,6 +212,7 @@ The plugin has several configuration option:
* `fallback_to_get`: Fallback to `GET` if `POST` fails. Defaults to True
* `format`: format of image to generate (`png`, `svg`, `svg_object`, `svg_inline` or `txt`). Defaults to `png` (See example section above for further explanations of the values for `format`)
* `http_method`: Http Method for server - `GET` or `POST`. "Defaults to `GET`
+* `image_maps`: generate image maps if format is `png` and the diagram has hyperlinks; `true`, `on`, `yes` or `1` activates image maps, everything else disables it. Defaults to `true`
* `priority`: extension priority. Higher values means the extension is applied sooner than others. Defaults to `30`
* `puml_notheme_cmdlist`: theme will not be set if listed commands present. Default list is `['version', 'listfonts', 'stdlib', 'license']`. **If modifying please copy the default list provided and append**
* `server`: PlantUML server url, for remote rendering. Defaults to `''`, use local command
diff --git a/plantuml_markdown.py b/plantuml_markdown.py
index 66c3eec..e27a904 100644
--- a/plantuml_markdown.py
+++ b/plantuml_markdown.py
@@ -218,16 +218,18 @@ def _replace_block(self, text):
data = 'data:image/png;base64,{0}'.format(base64.b64encode(diagram).decode('ascii'))
img = etree.Element('img')
img.attrib['src'] = data
- # Check for hyperlinks
- map_data = self._render_diagram(code, 'map', base_dir).decode("utf-8")
- if map_data.startswith('
""" % self.FAKE_IMAGE),
self.UUID_REGEX.sub('"test"', self.COORDS_REGEX.sub(' coords="1,2,3,4"', self._stripImageData(self.md.convert(text)))))
+ def test_plantuml_map_disabled(self):
+ """
+ Test map markup is not generated when disabled
+ """
+ include_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
+ configs = {
+ 'plantuml_markdown': {
+ 'base_dir': include_path,
+ 'image_maps': 'false'
+ }
+ }
+ self.md = markdown.Markdown(extensions=['markdown.extensions.fenced_code',
+ 'pymdownx.snippets', 'plantuml_markdown'],
+ extension_configs=configs)
+
+ text = self.text_builder.diagram('A --> B [[https://www.google.fr]]').build()
+ result = self._stripImageData(self.md.convert(text))
+ self.assertFalse("
Date: Tue, 4 Oct 2022 20:38:22 +0200
Subject: [PATCH 2/5] new: usr: added kroki as rendering server (refs #75)
With the plugin configuration `kroki_Server` is now possible to use a
Kroki server fore remote rendering.
Image maps are not supported by Kroki.
---
README.md | 3 ++-
plantuml_markdown.py | 25 ++++++++++++++++++++-----
test/test_plantuml.py | 23 ++++++++++++++++++++++-
3 files changed, 44 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 483264d..ab3103c 100644
--- a/README.md
+++ b/README.md
@@ -212,7 +212,8 @@ The plugin has several configuration option:
* `fallback_to_get`: Fallback to `GET` if `POST` fails. Defaults to True
* `format`: format of image to generate (`png`, `svg`, `svg_object`, `svg_inline` or `txt`). Defaults to `png` (See example section above for further explanations of the values for `format`)
* `http_method`: Http Method for server - `GET` or `POST`. "Defaults to `GET`
-* `image_maps`: generate image maps if format is `png` and the diagram has hyperlinks; `true`, `on`, `yes` or `1` activates image maps, everything else disables it. Defaults to `true`
+* `image_maps`: generate image maps if format is `png` and the diagram has hyperlinks; `true`, `on`, `yes` or `1` activates image maps, everything else disables it. Defaults to `true`
+* `kroki_server`: Kroki server url, as alternative to `server` for remote rendering (image maps not supported). Defaults to `''`, use PlantUML server if defined
* `priority`: extension priority. Higher values means the extension is applied sooner than others. Defaults to `30`
* `puml_notheme_cmdlist`: theme will not be set if listed commands present. Default list is `['version', 'listfonts', 'stdlib', 'license']`. **If modifying please copy the default list provided and append**
* `server`: PlantUML server url, for remote rendering. Defaults to `''`, use local command
diff --git a/plantuml_markdown.py b/plantuml_markdown.py
index e27a904..68a1d5e 100644
--- a/plantuml_markdown.py
+++ b/plantuml_markdown.py
@@ -219,7 +219,9 @@ def _replace_block(self, text):
img = etree.Element('img')
img.attrib['src'] = data
- if str(self.config['image_maps']).lower() in ['true', 'on', 'yes', '1']:
+ # image maps are not supported by Kroki
+ if not self.config['kroki_server'] and \
+ str(self.config['image_maps']).lower() in ['true', 'on', 'yes', '1']:
# Check for hyperlinks
map_data = self._render_diagram(code, 'map', base_dir).decode("utf-8")
if map_data.startswith(' str:
return base64.b64encode(zlibbed_str[2:-4]).translate(b64_to_plantuml).decode('utf-8')
+ @staticmethod
+ def _compress_and_encode(source: str) -> str:
+ # diagram encoding for Kroki
+ return base64.urlsafe_b64encode(zlib.compress(source.encode('utf-8'), 9)).decode('utf-8')
+
class PlantUMLIncluder:
@@ -406,7 +419,7 @@ def readFile(self, plantuml_code: str, directory: str) -> str:
lines = plantuml_code.splitlines()
# Wrap the whole combined text between startuml and enduml tags as recursive processing would have removed them
# This is necessary for it to work correctly with plamtuml POST processing
- return "@startuml\n" + "\n".join(self._readFileRec(lines, directory)) + "@enduml\n"
+ return "@startuml\n" + "\n".join(self._readFileRec(lines, directory)) + "\n@enduml\n"
# Reads the file recursively
def _readFileRec(self, lines: List[str], directory: str) -> List[str]:
@@ -481,6 +494,8 @@ def __init__(self, **kwargs):
'format': ["png", "Format of image to generate (png, svg or txt). Defaults to 'png'."],
'title': ["", "Tooltip for the diagram"],
'server': ["", "PlantUML server url, for remote rendering. Defaults to '', use local command."],
+ 'kroki_server': ["", "Kroki server url, as alternative to 'server' for remote rendering (image maps not "
+ "supported). Defaults to '', use PlantUML server if defined"],
'cachedir': ["", "Directory for caching of diagrams. Defaults to '', no caching"],
'image_maps': ["true", "Enable generation of PNG image maps, allowing to use hyperlinks with PNG images."
"Defaults to true"],
diff --git a/test/test_plantuml.py b/test/test_plantuml.py
index b983024..7f53a52 100644
--- a/test/test_plantuml.py
+++ b/test/test_plantuml.py
@@ -149,7 +149,7 @@ def _test_snippets(self, priority, expected):
from test.markdown_builder import MarkdownBuilder
from plantuml_markdown import PlantUMLPreprocessor
- # mcking a method to capture the generated PlantUML source code
+ # mocking a method to capture the generated PlantUML source code
with mock.patch.object(PlantUMLPreprocessor, '_render_diagram',
return_value='testing'.encode('utf8')) as mocked_plugin:
text = self.text_builder.diagram("--8<-- \"" + defs_file + "\"").build()
@@ -623,3 +623,24 @@ def test_include(self):
}
})
self.assertEqual('A -> B -> C
', self.md.convert(text))
+
+ def test_kroki(self):
+ """
+ Test calling a kroki server for rendering
+ """
+ with ServedBaseHTTPServerMock() as kroki_server_mock:
+ kroki_server_mock.responses[MethodName.GET].append(
+ MockHTTPResponse(status_code=200, headers={}, reason_phrase='', body=b"dummy")
+ )
+ self.md = markdown.Markdown(extensions=['plantuml_markdown'],
+ extension_configs={
+ 'plantuml_markdown': {
+ 'kroki_server': kroki_server_mock.url,
+ }
+ })
+ text = self.text_builder.diagram('A -> B').format('png').build()
+
+ self.assertEqual(self._stripImageData(self._load_file('png_diag.html')),
+ self._stripImageData(self.md.convert(text)))
+ req = kroki_server_mock.requests[MethodName.GET].pop(0)
+ self.assertTrue(req.path.startswith('/plantuml/png/'))
From d61fd6d10911242939336d4e8f423dc340d99103 Mon Sep 17 00:00:00 2001
From: Michele Tessaro
Date: Tue, 4 Oct 2022 20:41:54 +0200
Subject: [PATCH 3/5] chg: doc: regenerated changelog
---
CHANGELOG.md | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d2e24a..5100a80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,25 @@
# Changelog
+## development (unreleased)
+
+### New
+
+* Added kroki as rendering server (refs #75) [Michele Tessaro]
+
+ With the plugin configuration `kroki_Server` is now possible to use a
+ Kroki server fore remote rendering.
+ Image maps are not supported by Kroki.
+
+* Added option to disable image maps (refs #74) [Michele Tessaro]
+
+
## 3.6.3 (2022-08-01)
### Fix
+* Fixed yaml renderingwith remote server (fixes #72) [Michele Tessaro]
+
* Removed unused `plantuml` import. [Michele Tessaro]
* Doc: fix typos. [Kian-Meng Ang]
From 5c1fa58f07dc54cebcb2dec3c809eca602c49a02 Mon Sep 17 00:00:00 2001
From: Michele Tessaro
Date: Wed, 5 Oct 2022 20:26:40 +0200
Subject: [PATCH 4/5] new: usr: exposed error messages from kroki (refs #75)
Error messages from Kroki server are rendered as text in the output.
This is to overcome the problem that Kroki does not render errors as
images as PluntUML does.
---
README.md | 2 +-
plantuml_markdown.py | 174 +++++++++++++++++++++---------------------
test/test_plantuml.py | 3 +-
3 files changed, 92 insertions(+), 87 deletions(-)
diff --git a/README.md b/README.md
index ab3103c..12708fb 100644
--- a/README.md
+++ b/README.md
@@ -213,7 +213,7 @@ The plugin has several configuration option:
* `format`: format of image to generate (`png`, `svg`, `svg_object`, `svg_inline` or `txt`). Defaults to `png` (See example section above for further explanations of the values for `format`)
* `http_method`: Http Method for server - `GET` or `POST`. "Defaults to `GET`
* `image_maps`: generate image maps if format is `png` and the diagram has hyperlinks; `true`, `on`, `yes` or `1` activates image maps, everything else disables it. Defaults to `true`
-* `kroki_server`: Kroki server url, as alternative to `server` for remote rendering (image maps not supported). Defaults to `''`, use PlantUML server if defined
+* `kroki_server`: Kroki server url, as alternative to `server` for remote rendering (image maps mus be disabled manually). Defaults to `''`, use PlantUML server if defined
* `priority`: extension priority. Higher values means the extension is applied sooner than others. Defaults to `30`
* `puml_notheme_cmdlist`: theme will not be set if listed commands present. Default list is `['version', 'listfonts', 'stdlib', 'license']`. **If modifying please copy the default list provided and append**
* `server`: PlantUML server url, for remote rendering. Defaults to `''`, use local command
diff --git a/plantuml_markdown.py b/plantuml_markdown.py
index 68a1d5e..0033832 100644
--- a/plantuml_markdown.py
+++ b/plantuml_markdown.py
@@ -61,7 +61,7 @@
import zlib
import string
from subprocess import Popen, PIPE
-from typing import Dict, List, Optional
+from typing import Dict, List, Optional, Tuple
from zlib import adler32
import logging
@@ -186,87 +186,91 @@ def _replace_block(self, text):
code += m.group('code')
# Extract diagram source end convert it (if not external)
- diagram = self._render_diagram(code, requested_format, base_dir)
- self_closed = True # tags are always self closing
- map_tag = ''
-
- if img_format == 'txt':
- # logger.debug(diagram)
- img = etree.Element('pre')
- code = etree.SubElement(img, 'code')
- code.attrib['class'] = 'text'
- code.text = AtomicString(diagram.decode('UTF-8'))
+ diagram, err = self._render_diagram(code, requested_format, base_dir)
+
+ if err:
+ # there is an error message: create a nice tag to show it
+ diag_tag = f'{err}
'
else:
- # These are images
- if img_format == 'svg_inline':
- data = self.ADAPT_SVG_REGEX.sub('', diagram.decode('UTF-8'))
- img = etree.fromstring(data.encode('UTF-8'))
- # remove width and height in style attribute
- img.attrib['style'] = re.sub(r'\b(?:width|height):\d+px;', '', img.attrib['style'])
- elif img_format == 'svg':
- # Firefox handles only base64 encoded SVGs
- data = 'data:image/svg+xml;base64,{0}'.format(base64.b64encode(diagram).decode('ascii'))
- img = etree.Element('img')
- img.attrib['src'] = data
- elif img_format == 'svg_object':
- # Firefox handles only base64 encoded SVGs
- data = 'data:image/svg+xml;base64,{0}'.format(base64.b64encode(diagram).decode('ascii'))
- img = etree.Element('object')
- img.attrib['data'] = data
- self_closed = False # object tag must be explicitly closed
- else: # png format, explicitly set or as a default when format is not recognized
- data = 'data:image/png;base64,{0}'.format(base64.b64encode(diagram).decode('ascii'))
- img = etree.Element('img')
- img.attrib['src'] = data
-
- # image maps are not supported by Kroki
- if not self.config['kroki_server'] and \
- str(self.config['image_maps']).lower() in ['true', 'on', 'yes', '1']:
- # Check for hyperlinks
- map_data = self._render_diagram(code, 'map', base_dir).decode("utf-8")
- if map_data.startswith('', diagram.decode('UTF-8'))
+ img = etree.fromstring(data.encode('UTF-8'))
+ # remove width and height in style attribute
+ img.attrib['style'] = re.sub(r'\b(?:width|height):\d+px;', '', img.attrib['style'])
+ elif img_format == 'svg':
+ # Firefox handles only base64 encoded SVGs
+ data = 'data:image/svg+xml;base64,{0}'.format(base64.b64encode(diagram).decode('ascii'))
+ img = etree.Element('img')
+ img.attrib['src'] = data
+ elif img_format == 'svg_object':
+ # Firefox handles only base64 encoded SVGs
+ data = 'data:image/svg+xml;base64,{0}'.format(base64.b64encode(diagram).decode('ascii'))
+ img = etree.Element('object')
+ img.attrib['data'] = data
+ self_closed = False # object tag must be explicitly closed
+ else: # png format, explicitly set or as a default when format is not recognized
+ data = 'data:image/png;base64,{0}'.format(base64.b64encode(diagram).decode('ascii'))
+ img = etree.Element('img')
+ img.attrib['src'] = data
+
+ # check if image maps are enabled
+ if str(self.config['image_maps']).lower() in ['true', 'on', 'yes', '1']:
+ # Check for hyperlinks
+ map_data, err = self._render_diagram(code, 'map', base_dir)
+ map_data = map_data.decode("utf-8")
+
+ if map_data.startswith(' Tuple[any, Optional[str]]:
cached_diagram_file = None
diagram = None
if self.config['cachedir']:
diagram_hash = "%08x" % (adler32(code.encode('UTF-8')) & 0xffffffff)
cached_diagram_file = os.path.expanduser(
- os.path.join(
- self.config['cachedir'],
- diagram_hash + '.' + requested_format))
+ os.path.join(self.config['cachedir'], diagram_hash + '.' + requested_format))
if os.path.isfile(cached_diagram_file):
with open(cached_diagram_file, 'rb') as f:
@@ -274,21 +278,21 @@ def _render_diagram(self, code, requested_format, base_dir):
if diagram:
# if cache found then end this function here
- return diagram
+ return diagram, None
# if cache not found create the diagram
code = self._set_theme(code)
if self.config['server'] or self.config['kroki_server']:
- diagram = self._render_remote_uml_image(code, requested_format, base_dir)
+ diagram, err = self._render_remote_uml_image(code, requested_format, base_dir)
else:
- diagram = self._render_local_uml_image(code, requested_format)
+ diagram, err = self._render_local_uml_image(code, requested_format)
- if self.config['cachedir']:
+ if not err and self.config['cachedir']:
with open(cached_diagram_file, 'wb') as f:
f.write(diagram)
- return diagram
+ return diagram, err
def _set_theme(self, code):
theme = self.config['theme'].strip()
@@ -337,7 +341,7 @@ def _set_theme(self, code):
return code
@staticmethod
- def _render_local_uml_image(plantuml_code, img_format):
+ def _render_local_uml_image(plantuml_code: str, img_format: str) -> Tuple[any, Optional[str]]:
plantuml_code = plantuml_code.encode('utf8')
cmdline = ['plantuml', '-pipemap' if img_format == 'map' else '-p', "-t" + img_format]
@@ -345,7 +349,6 @@ def _render_local_uml_image(plantuml_code, img_format):
# On Windows run batch files through a shell so the extension can be resolved
p = Popen(cmdline, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=(os.name == 'nt'))
out, err = p.communicate(input=plantuml_code)
-
except Exception as exc:
raise Exception('Failed to run plantuml: %s' % exc)
else:
@@ -353,9 +356,9 @@ def _render_local_uml_image(plantuml_code, img_format):
# plantuml returns a nice image in case of syntax error so log but still return out
logger.error('Error in "uml" directive: %s' % err)
- return out
+ return out, None
- def _render_remote_uml_image(self, plantuml_code, img_format, base_dir):
+ def _render_remote_uml_image(self, plantuml_code: str, img_format: str, base_dir: str) -> Tuple[any, Optional[str]]:
# build the whole source diagram, executing include directives
temp_file = PlantUMLIncluder(False).readFile(plantuml_code, base_dir)
http_method = self.config['http_method'].strip()
@@ -377,7 +380,7 @@ def _render_remote_uml_image(self, plantuml_code, img_format, base_dir):
logger.error('Falling back to Get')
post_failed = True
if not post_failed:
- return r.content
+ return r.content, None
if http_method == "GET" or post_failed:
if self.config['kroki_server']:
@@ -389,9 +392,10 @@ def _render_remote_uml_image(self, plantuml_code, img_format, base_dir):
with requests.get(image_url) as r:
if not r.ok:
- logger.warning('WARNING in "uml" directive: remote server has returned error %d on GET' % r.status_code)
+ logger.warning(f'WARNING in "uml" directive: remote server has returned error %d on GET: %s' % (r.status_code, r.content.decode('utf-8')))
+ return None, r.content.decode('utf-8')
- return r.content
+ return r.content, None
@staticmethod
def _deflate_and_encode(source: str) -> str:
@@ -494,8 +498,8 @@ def __init__(self, **kwargs):
'format': ["png", "Format of image to generate (png, svg or txt). Defaults to 'png'."],
'title': ["", "Tooltip for the diagram"],
'server': ["", "PlantUML server url, for remote rendering. Defaults to '', use local command."],
- 'kroki_server': ["", "Kroki server url, as alternative to 'server' for remote rendering (image maps not "
- "supported). Defaults to '', use PlantUML server if defined"],
+ 'kroki_server': ["", "Kroki server url, as alternative to 'server' for remote rendering (image maps must "
+ "be disabled manually). Defaults to '', use PlantUML server if defined"],
'cachedir': ["", "Directory for caching of diagrams. Defaults to '', no caching"],
'image_maps': ["true", "Enable generation of PNG image maps, allowing to use hyperlinks with PNG images."
"Defaults to true"],
diff --git a/test/test_plantuml.py b/test/test_plantuml.py
index 7f53a52..2aa82c4 100644
--- a/test/test_plantuml.py
+++ b/test/test_plantuml.py
@@ -151,7 +151,7 @@ def _test_snippets(self, priority, expected):
# mocking a method to capture the generated PlantUML source code
with mock.patch.object(PlantUMLPreprocessor, '_render_diagram',
- return_value='testing'.encode('utf8')) as mocked_plugin:
+ return_value=('testing'.encode('utf8'), None)) as mocked_plugin:
text = self.text_builder.diagram("--8<-- \"" + defs_file + "\"").build()
self.md.convert(text)
mocked_plugin.assert_called_with(expected, 'map', '.')
@@ -636,6 +636,7 @@ def test_kroki(self):
extension_configs={
'plantuml_markdown': {
'kroki_server': kroki_server_mock.url,
+ 'image_maps': 'no'
}
})
text = self.text_builder.diagram('A -> B').format('png').build()
From fc736d6fef577c65de080db7fdfeea31740a2701 Mon Sep 17 00:00:00 2001
From: Michele Tessaro
Date: Wed, 5 Oct 2022 20:32:56 +0200
Subject: [PATCH 5/5] chg: pkg: changed version for the new release
---
CHANGELOG.md | 12 +++++++++++-
setup.py | 2 +-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5100a80..4a5b5d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,16 @@
# Changelog
-## development (unreleased)
+## 3.7.0 (2022-10-05)
### New
+* Exposed error messages from kroki (refs #75) [Michele Tessaro]
+
+ Error messages from Kroki server are rendered as text in the output.
+ This is to overcome the problem that Kroki does not render errors as
+ images as PluntUML does.
+
* Added kroki as rendering server (refs #75) [Michele Tessaro]
With the plugin configuration `kroki_Server` is now possible to use a
@@ -13,6 +19,10 @@
* Added option to disable image maps (refs #74) [Michele Tessaro]
+### Changes
+
+* Regenerated changelog. [Michele Tessaro]
+
## 3.6.3 (2022-08-01)
diff --git a/setup.py b/setup.py
index 20b9368..5c71c4f 100644
--- a/setup.py
+++ b/setup.py
@@ -15,7 +15,7 @@
setuptools.setup(
name="plantuml-markdown",
- version="3.6.3",
+ version="3.7.0",
author="Michele Tessaro",
author_email="michele.tessaro@email.it",
description="A PlantUML plugin for Markdown",