markdown-to-confluence 0.5.2__py3-none-any.whl → 0.5.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.4.dist-info}/METADATA +258 -157
- markdown_to_confluence-0.5.4.dist-info/RECORD +55 -0
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.4.dist-info}/licenses/LICENSE +1 -1
- md2conf/__init__.py +2 -2
- md2conf/__main__.py +83 -44
- md2conf/api.py +30 -10
- md2conf/attachment.py +72 -0
- md2conf/coalesce.py +43 -0
- md2conf/collection.py +1 -1
- md2conf/{extra.py → compatibility.py} +1 -1
- md2conf/converter.py +240 -657
- md2conf/csf.py +13 -11
- md2conf/drawio/__init__.py +0 -0
- md2conf/drawio/extension.py +116 -0
- md2conf/{drawio.py → drawio/render.py} +1 -1
- md2conf/emoticon.py +3 -3
- md2conf/environment.py +2 -2
- md2conf/extension.py +82 -0
- md2conf/external.py +66 -0
- md2conf/formatting.py +135 -0
- md2conf/frontmatter.py +70 -0
- md2conf/image.py +128 -0
- md2conf/latex.py +4 -183
- md2conf/local.py +8 -8
- md2conf/markdown.py +1 -1
- md2conf/matcher.py +1 -1
- md2conf/mermaid/__init__.py +0 -0
- md2conf/mermaid/config.py +20 -0
- md2conf/mermaid/extension.py +109 -0
- md2conf/{mermaid.py → mermaid/render.py} +10 -38
- md2conf/mermaid/scanner.py +55 -0
- md2conf/metadata.py +1 -1
- md2conf/{domain.py → options.py} +75 -16
- md2conf/plantuml/__init__.py +0 -0
- md2conf/plantuml/config.py +20 -0
- md2conf/plantuml/extension.py +158 -0
- md2conf/plantuml/render.py +138 -0
- md2conf/plantuml/scanner.py +56 -0
- md2conf/png.py +206 -0
- md2conf/processor.py +55 -13
- md2conf/publisher.py +127 -39
- md2conf/scanner.py +38 -129
- md2conf/serializer.py +2 -2
- md2conf/svg.py +144 -103
- md2conf/text.py +1 -1
- md2conf/toc.py +73 -1
- md2conf/uri.py +1 -1
- md2conf/xml.py +1 -1
- markdown_to_confluence-0.5.2.dist-info/RECORD +0 -36
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.4.dist-info}/WHEEL +0 -0
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.4.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.4.dist-info}/top_level.txt +0 -0
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.4.dist-info}/zip-safe +0 -0
- /md2conf/{puppeteer-config.json → mermaid/puppeteer-config.json} +0 -0
md2conf/metadata.py
CHANGED
md2conf/{domain.py → options.py}
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Publish Markdown files to Confluence wiki.
|
|
3
3
|
|
|
4
|
-
Copyright 2022-
|
|
4
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
5
5
|
|
|
6
6
|
:see: https://github.com/hunyadi/md2conf
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import dataclasses
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from typing import Literal
|
|
11
12
|
|
|
@@ -16,44 +17,102 @@ class ConfluencePageID:
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@dataclass
|
|
19
|
-
class
|
|
20
|
+
class ImageLayoutOptions:
|
|
20
21
|
"""
|
|
21
|
-
|
|
22
|
+
Image layout options on a Confluence page.
|
|
23
|
+
|
|
24
|
+
:param alignment: Alignment for block-level images and formulas.
|
|
25
|
+
:param max_width: Maximum display width for images [px]. Wider images are scaled down for page display. Original size kept for full-size viewing.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
alignment: Literal["center", "left", "right"] | None = None
|
|
29
|
+
max_width: int | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class TableLayoutOptions:
|
|
34
|
+
"""
|
|
35
|
+
Table layout options on a Confluence page.
|
|
36
|
+
|
|
37
|
+
:param width: Maximum table width in pixels.
|
|
38
|
+
:param display_mode: Whether to use fixed or responsive column widths.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
width: int | None = None
|
|
42
|
+
display_mode: Literal["fixed", "responsive"] | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class LayoutOptions:
|
|
47
|
+
"""
|
|
48
|
+
Layout options for content on a Confluence page.
|
|
49
|
+
|
|
50
|
+
Layout options can be overridden in Markdown front-matter.
|
|
51
|
+
|
|
52
|
+
:param image: Image layout options.
|
|
53
|
+
:param table: Table layout options.
|
|
54
|
+
:param alignment: Default alignment (unless overridden with more specific setting).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
image: ImageLayoutOptions = dataclasses.field(default_factory=ImageLayoutOptions)
|
|
58
|
+
table: TableLayoutOptions = dataclasses.field(default_factory=TableLayoutOptions)
|
|
59
|
+
alignment: Literal["center", "left", "right"] | None = None
|
|
60
|
+
|
|
61
|
+
def get_image_alignment(self) -> Literal["center", "left", "right"]:
|
|
62
|
+
return self.image.alignment or self.alignment or "center"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class ConverterOptions:
|
|
67
|
+
"""
|
|
68
|
+
Options for converting an HTML tree into Confluence Storage Format.
|
|
22
69
|
|
|
23
70
|
:param heading_anchors: When true, emit a structured macro *anchor* for each section heading using GitHub
|
|
24
71
|
conversion rules for the identifier.
|
|
25
72
|
:param ignore_invalid_url: When true, ignore invalid URLs in input, emit a warning and replace the anchor with
|
|
26
73
|
plain text; when false, raise an exception.
|
|
27
74
|
:param skip_title_heading: Whether to remove the first heading from document body when used as page title.
|
|
28
|
-
:param title_prefix: String to prepend to Confluence page title for each published page.
|
|
29
|
-
:param generated_by: Text to use as the generated-by prompt (or `None` to omit a prompt).
|
|
30
|
-
:param root_page_id: Confluence page to assume root page role for publishing a directory of Markdown files.
|
|
31
|
-
:param keep_hierarchy: Whether to maintain source directory structure when exporting to Confluence.
|
|
32
75
|
:param prefer_raster: Whether to choose PNG files over SVG files when available.
|
|
33
76
|
:param render_drawio: Whether to pre-render (or use the pre-rendered version of) draw.io diagrams.
|
|
34
77
|
:param render_mermaid: Whether to pre-render Mermaid diagrams into PNG/SVG images.
|
|
78
|
+
:param render_plantuml: Whether to pre-render PlantUML diagrams into PNG/SVG images.
|
|
35
79
|
:param render_latex: Whether to pre-render LaTeX formulas into PNG/SVG images.
|
|
36
80
|
:param diagram_output_format: Target image format for diagrams.
|
|
37
81
|
:param webui_links: When true, convert relative URLs to Confluence Web UI links.
|
|
38
|
-
:param alignment: Alignment for block-level images and formulas.
|
|
39
|
-
:param max_image_width: Maximum display width for images [px]. Wider images are scaled down for page display.
|
|
40
|
-
Original size kept for full-size viewing.
|
|
41
82
|
:param use_panel: Whether to transform admonitions and alerts into a Confluence custom panel.
|
|
83
|
+
:param layout: Layout options for content on a Confluence page.
|
|
42
84
|
"""
|
|
43
85
|
|
|
44
86
|
heading_anchors: bool = False
|
|
45
87
|
ignore_invalid_url: bool = False
|
|
46
88
|
skip_title_heading: bool = False
|
|
47
|
-
title_prefix: str | None = None
|
|
48
|
-
generated_by: str | None = "This page has been generated with a tool."
|
|
49
|
-
root_page_id: ConfluencePageID | None = None
|
|
50
|
-
keep_hierarchy: bool = False
|
|
51
89
|
prefer_raster: bool = True
|
|
52
90
|
render_drawio: bool = False
|
|
53
91
|
render_mermaid: bool = False
|
|
92
|
+
render_plantuml: bool = False
|
|
54
93
|
render_latex: bool = False
|
|
55
94
|
diagram_output_format: Literal["png", "svg"] = "png"
|
|
56
95
|
webui_links: bool = False
|
|
57
|
-
alignment: Literal["center", "left", "right"] = "center"
|
|
58
|
-
max_image_width: int | None = None
|
|
59
96
|
use_panel: bool = False
|
|
97
|
+
layout: LayoutOptions = dataclasses.field(default_factory=LayoutOptions)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class DocumentOptions:
|
|
102
|
+
"""
|
|
103
|
+
Options that control the generated page content.
|
|
104
|
+
|
|
105
|
+
:param root_page_id: Confluence page to assume root page role for publishing a directory of Markdown files.
|
|
106
|
+
:param keep_hierarchy: Whether to maintain source directory structure when exporting to Confluence.
|
|
107
|
+
:param title_prefix: String to prepend to Confluence page title for each published page.
|
|
108
|
+
:param generated_by: Text to use as the generated-by prompt (or `None` to omit a prompt).
|
|
109
|
+
:param skip_update: Whether to skip saving Confluence page ID in Markdown files.
|
|
110
|
+
:param converter: Options for converting an HTML tree into Confluence Storage Format.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
root_page_id: ConfluencePageID | None = None
|
|
114
|
+
keep_hierarchy: bool = False
|
|
115
|
+
title_prefix: str | None = None
|
|
116
|
+
generated_by: str | None = "This page has been generated with a tool."
|
|
117
|
+
skip_update: bool = False
|
|
118
|
+
converter: ConverterOptions = dataclasses.field(default_factory=ConverterOptions)
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class PlantUMLConfigProperties:
|
|
14
|
+
"""
|
|
15
|
+
Configuration options for rendering PlantUML diagrams.
|
|
16
|
+
|
|
17
|
+
:param scale: Scaling factor for the rendered diagram.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
scale: float | None = None
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import logging
|
|
11
|
+
import uuid
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import lxml.etree as ET
|
|
15
|
+
from cattrs import BaseValidationError
|
|
16
|
+
|
|
17
|
+
from md2conf.attachment import EmbeddedFileData, ImageData, attachment_name
|
|
18
|
+
from md2conf.compatibility import override, path_relative_to
|
|
19
|
+
from md2conf.csf import AC_ATTR, AC_ELEM
|
|
20
|
+
from md2conf.extension import MarketplaceExtension
|
|
21
|
+
from md2conf.formatting import ImageAttributes
|
|
22
|
+
from md2conf.svg import get_svg_dimensions
|
|
23
|
+
|
|
24
|
+
from .config import PlantUMLConfigProperties
|
|
25
|
+
from .render import compress_plantuml_data, has_plantuml, render_diagram
|
|
26
|
+
from .scanner import PlantUMLScanner
|
|
27
|
+
|
|
28
|
+
ElementType = ET._Element # pyright: ignore [reportPrivateUsage]
|
|
29
|
+
|
|
30
|
+
LOGGER = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PlantUMLExtension(MarketplaceExtension):
|
|
34
|
+
@override
|
|
35
|
+
def matches_image(self, absolute_path: Path) -> bool:
|
|
36
|
+
return absolute_path.name.endswith((".puml", ".plantuml"))
|
|
37
|
+
|
|
38
|
+
@override
|
|
39
|
+
def matches_fenced(self, language: str, content: str) -> bool:
|
|
40
|
+
return language == "plantuml"
|
|
41
|
+
|
|
42
|
+
def _extract_plantuml_config(self, content: str) -> PlantUMLConfigProperties | None:
|
|
43
|
+
"Extract config from PlantUML YAML front matter configuration."
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
properties = PlantUMLScanner().read(content)
|
|
47
|
+
return properties.config
|
|
48
|
+
except BaseValidationError as ex:
|
|
49
|
+
LOGGER.warning("Failed to extract PlantUML properties: %s", ex)
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
def transform_image(self, absolute_path: Path, attrs: ImageAttributes) -> ElementType:
|
|
54
|
+
relative_path = path_relative_to(absolute_path, self.base_dir)
|
|
55
|
+
|
|
56
|
+
# read PlantUML source
|
|
57
|
+
with open(absolute_path, "r", encoding="utf-8") as f:
|
|
58
|
+
content = f.read()
|
|
59
|
+
|
|
60
|
+
return self._transform_plantuml(content, attrs, relative_path)
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
def transform_fenced(self, content: str) -> ElementType:
|
|
64
|
+
return self._transform_plantuml(content, ImageAttributes.EMPTY_BLOCK)
|
|
65
|
+
|
|
66
|
+
def _transform_plantuml(self, content: str, attrs: ImageAttributes, relative_path: Path | None = None) -> ElementType:
|
|
67
|
+
"""
|
|
68
|
+
Emits Confluence Storage Format XHTML for a PlantUML diagram read from an external file or defined in a fenced code block.
|
|
69
|
+
|
|
70
|
+
When `render_plantuml` is enabled, renders as an image attachment. Otherwise, uses a structured macro with embedded SVG and compressed source.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
if self.options.render:
|
|
74
|
+
# render diagram as image file (PNG or SVG based on diagram output format)
|
|
75
|
+
config = self._extract_plantuml_config(content)
|
|
76
|
+
image_data = render_diagram(content, self.generator.options.output_format, config=config)
|
|
77
|
+
return self.generator.transform_attached_data(image_data, attrs, relative_path)
|
|
78
|
+
else:
|
|
79
|
+
if relative_path is not None:
|
|
80
|
+
absolute_path = self.base_dir / relative_path
|
|
81
|
+
self.attachments.add_image(ImageData(absolute_path, attrs.alt))
|
|
82
|
+
|
|
83
|
+
# use `structured-macro` with SVG attachment
|
|
84
|
+
if has_plantuml():
|
|
85
|
+
# render to SVG for structured macro (macro requires SVG)
|
|
86
|
+
config = self._extract_plantuml_config(content)
|
|
87
|
+
image_data = render_diagram(content, "svg", config=config)
|
|
88
|
+
|
|
89
|
+
# extract dimensions from SVG
|
|
90
|
+
dimensions = get_svg_dimensions(image_data)
|
|
91
|
+
|
|
92
|
+
# generate SVG filename and add as attachment
|
|
93
|
+
if relative_path is not None:
|
|
94
|
+
svg_filename = attachment_name(relative_path.with_suffix(".svg"))
|
|
95
|
+
self.attachments.add_embed(svg_filename, EmbeddedFileData(image_data, attrs.alt))
|
|
96
|
+
else:
|
|
97
|
+
plantuml_hash = hashlib.md5(content.encode("utf-8")).hexdigest()
|
|
98
|
+
svg_filename = attachment_name(f"embedded_{plantuml_hash}.svg")
|
|
99
|
+
self.attachments.add_embed(svg_filename, EmbeddedFileData(image_data))
|
|
100
|
+
|
|
101
|
+
return self._create_plantuml_macro(content, svg_filename, dimensions)
|
|
102
|
+
else:
|
|
103
|
+
return self._create_plantuml_macro(content)
|
|
104
|
+
|
|
105
|
+
def _create_plantuml_macro(self, source: str, filename: str | None = None, dimensions: tuple[int, int] | None = None) -> ElementType:
|
|
106
|
+
"""
|
|
107
|
+
A PlantUML diagram using a `structured-macro` with embedded data.
|
|
108
|
+
|
|
109
|
+
Generates a macro compatible with "PlantUML Diagrams for Confluence" app.
|
|
110
|
+
|
|
111
|
+
:see: https://stratus-addons.atlassian.net/wiki/spaces/PDFC/pages/1839333377
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
local_id = str(uuid.uuid4())
|
|
115
|
+
macro_id = str(uuid.uuid4())
|
|
116
|
+
|
|
117
|
+
# Compress PlantUML source for embedding
|
|
118
|
+
compressed_data = compress_plantuml_data(source)
|
|
119
|
+
|
|
120
|
+
# Build mandatory parameters
|
|
121
|
+
parameters: list[ElementType] = [
|
|
122
|
+
AC_ELEM("parameter", {AC_ATTR("name"): "data"}, compressed_data),
|
|
123
|
+
AC_ELEM("parameter", {AC_ATTR("name"): "compressed"}, "true"),
|
|
124
|
+
AC_ELEM("parameter", {AC_ATTR("name"): "revision"}, "1"),
|
|
125
|
+
AC_ELEM("parameter", {AC_ATTR("name"): "toolbar"}, "bottom"),
|
|
126
|
+
]
|
|
127
|
+
if filename is not None:
|
|
128
|
+
parameters.append(AC_ELEM("parameter", {AC_ATTR("name"): "filename"}, filename))
|
|
129
|
+
|
|
130
|
+
# add optional dimension parameters if available
|
|
131
|
+
if dimensions is not None:
|
|
132
|
+
width, height = dimensions
|
|
133
|
+
parameters.append(
|
|
134
|
+
AC_ELEM(
|
|
135
|
+
"parameter",
|
|
136
|
+
{AC_ATTR("name"): "originalWidth"},
|
|
137
|
+
str(width),
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
parameters.append(
|
|
141
|
+
AC_ELEM(
|
|
142
|
+
"parameter",
|
|
143
|
+
{AC_ATTR("name"): "originalHeight"},
|
|
144
|
+
str(height),
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return AC_ELEM(
|
|
149
|
+
"structured-macro",
|
|
150
|
+
{
|
|
151
|
+
AC_ATTR("name"): "plantumlcloud",
|
|
152
|
+
AC_ATTR("schema-version"): "1",
|
|
153
|
+
"data-layout": "default",
|
|
154
|
+
AC_ATTR("local-id"): local_id,
|
|
155
|
+
AC_ATTR("macro-id"): macro_id,
|
|
156
|
+
},
|
|
157
|
+
*parameters,
|
|
158
|
+
)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import shlex
|
|
13
|
+
import shutil
|
|
14
|
+
import zlib
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Literal
|
|
17
|
+
from urllib.parse import quote
|
|
18
|
+
|
|
19
|
+
import md2conf
|
|
20
|
+
from md2conf.external import execute_subprocess
|
|
21
|
+
|
|
22
|
+
from .config import PlantUMLConfigProperties
|
|
23
|
+
|
|
24
|
+
LOGGER = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_plantuml_jar_path() -> Path:
|
|
28
|
+
"""
|
|
29
|
+
Returns the expected path to `plantuml.jar`.
|
|
30
|
+
|
|
31
|
+
Priority:
|
|
32
|
+
|
|
33
|
+
1. value of environment variable `PLANTUML_JAR`
|
|
34
|
+
2. path to `plantuml.jar` if found in parent directory of module `md2conf`
|
|
35
|
+
3. path to `plantuml.jar` if found in current directory
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# check environment variable
|
|
39
|
+
env_jar = os.environ.get("PLANTUML_JAR")
|
|
40
|
+
if env_jar:
|
|
41
|
+
return Path(env_jar)
|
|
42
|
+
|
|
43
|
+
# check parent directory of module `md2conf`
|
|
44
|
+
base_path = Path(md2conf.__file__).parent.parent
|
|
45
|
+
jar_path = base_path / "plantuml.jar"
|
|
46
|
+
if jar_path.exists():
|
|
47
|
+
return jar_path
|
|
48
|
+
|
|
49
|
+
# check current directory
|
|
50
|
+
return Path("plantuml.jar")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _get_plantuml_command() -> list[str]:
|
|
54
|
+
"""
|
|
55
|
+
Returns the command to invoke PlantUML.
|
|
56
|
+
|
|
57
|
+
:raises RuntimeError: Raised when `plantuml.jar` is not found.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
env_cmd = os.environ.get("PLANTUML_CMD")
|
|
61
|
+
if env_cmd:
|
|
62
|
+
LOGGER.debug(f"Using PlantUML command: {env_cmd}")
|
|
63
|
+
return shlex.split(env_cmd)
|
|
64
|
+
|
|
65
|
+
jar_path = _get_plantuml_jar_path()
|
|
66
|
+
if jar_path.is_file():
|
|
67
|
+
LOGGER.debug(f"Using PlantUML JAR at: {jar_path}")
|
|
68
|
+
return ["java", "-jar", str(jar_path)]
|
|
69
|
+
|
|
70
|
+
# JAR not found - fail with helpful message
|
|
71
|
+
raise RuntimeError(
|
|
72
|
+
"PlantUML JAR not found. Download `plantuml.jar` from https://github.com/plantuml/plantuml/releases and set the PLANTUML_JAR environment variable."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def has_plantuml() -> bool:
|
|
77
|
+
"""True if PlantUML JAR is available and Java is installed."""
|
|
78
|
+
|
|
79
|
+
jar_path = _get_plantuml_jar_path()
|
|
80
|
+
|
|
81
|
+
# Check if we have JAR file and Java is available
|
|
82
|
+
return jar_path.is_file() and shutil.which("java") is not None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def render_diagram(
|
|
86
|
+
source: str,
|
|
87
|
+
output_format: Literal["png", "svg"] = "png",
|
|
88
|
+
config: PlantUMLConfigProperties | None = None,
|
|
89
|
+
) -> bytes:
|
|
90
|
+
"Generates a PNG or SVG image from a PlantUML diagram source."
|
|
91
|
+
|
|
92
|
+
if config is None:
|
|
93
|
+
config = PlantUMLConfigProperties()
|
|
94
|
+
|
|
95
|
+
# command for PlantUML with pipe mode
|
|
96
|
+
cmd = _get_plantuml_command()
|
|
97
|
+
cmd.extend(
|
|
98
|
+
[
|
|
99
|
+
"--charset",
|
|
100
|
+
"utf-8",
|
|
101
|
+
"--format",
|
|
102
|
+
output_format,
|
|
103
|
+
"--no-error-image",
|
|
104
|
+
"--pipe",
|
|
105
|
+
]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Add scale if specified
|
|
109
|
+
if config.scale is not None:
|
|
110
|
+
cmd.extend(["-scale", str(config.scale)])
|
|
111
|
+
|
|
112
|
+
return execute_subprocess(cmd, source.encode("utf-8"), application="PlantUML")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def compress_plantuml_data(source: str) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Compress PlantUML source for embedding in plantumlcloud macro.
|
|
118
|
+
|
|
119
|
+
Implements the encoding used by PlantUML Diagrams for Confluence:
|
|
120
|
+
|
|
121
|
+
1. URI encode the source
|
|
122
|
+
2. Deflate with raw deflate (zlib)
|
|
123
|
+
3. Base64 encode
|
|
124
|
+
|
|
125
|
+
:param source: PlantUML diagram source code.
|
|
126
|
+
:returns: Compressed and encoded data suitable for macro data parameter.
|
|
127
|
+
:see: https://stratus-addons.atlassian.net/wiki/spaces/PDFC/pages/1839333377
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
# Step 1: URI encode
|
|
131
|
+
encoded = quote(source, safe="")
|
|
132
|
+
|
|
133
|
+
# Step 2: Deflate with raw deflate (remove zlib header/trailer)
|
|
134
|
+
# zlib.compress() adds 2-byte header and 4-byte trailer
|
|
135
|
+
deflated = zlib.compress(encoded.encode("utf-8"))[2:-4]
|
|
136
|
+
|
|
137
|
+
# Step 3: Base64 encode
|
|
138
|
+
return base64.b64encode(deflated).decode("ascii")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from md2conf.frontmatter import extract_frontmatter_object
|
|
12
|
+
|
|
13
|
+
from .config import PlantUMLConfigProperties
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class PlantUMLProperties:
|
|
18
|
+
"""
|
|
19
|
+
An object that holds the front-matter properties structure
|
|
20
|
+
for PlantUML diagrams.
|
|
21
|
+
|
|
22
|
+
:param title: The title of the diagram.
|
|
23
|
+
:param config: Configuration options for rendering.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
title: str | None = None
|
|
27
|
+
config: PlantUMLConfigProperties | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PlantUMLScanner:
|
|
31
|
+
"""
|
|
32
|
+
Extracts properties from the JSON/YAML front-matter of a PlantUML diagram.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def read(self, content: str) -> PlantUMLProperties:
|
|
36
|
+
"""
|
|
37
|
+
Extracts rendering preferences from a PlantUML front-matter content.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
---
|
|
41
|
+
title: Class diagram
|
|
42
|
+
config:
|
|
43
|
+
scale: 1
|
|
44
|
+
---
|
|
45
|
+
@startuml
|
|
46
|
+
class Example
|
|
47
|
+
@enduml
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
properties, _ = extract_frontmatter_object(PlantUMLProperties, content)
|
|
52
|
+
if properties is not None:
|
|
53
|
+
config = properties.config or PlantUMLConfigProperties()
|
|
54
|
+
return PlantUMLProperties(title=properties.title, config=config)
|
|
55
|
+
|
|
56
|
+
return PlantUMLProperties()
|