mkdocs2confluence 0.16.0__tar.gz → 0.17.0__tar.gz
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.
- {mkdocs2confluence-0.16.0/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.17.0}/PKG-INFO +2 -2
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/README.md +1 -1
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/pyproject.toml +1 -1
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0/src/mkdocs2confluence.egg-info}/PKG-INFO +2 -2
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs2confluence.egg-info/SOURCES.txt +3 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/compiler/page.py +9 -3
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/emitter/xhtml.py +25 -3
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/ir/nodes.py +25 -6
- mkdocs2confluence-0.17.0/src/mkdocs_to_confluence/parser/__init__.py +5 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/parser/markdown.py +13 -0
- mkdocs2confluence-0.17.0/src/mkdocs_to_confluence/transforms/_kroki.py +132 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/abbrevs.py +10 -6
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/mermaid.py +15 -59
- mkdocs2confluence-0.17.0/src/mkdocs_to_confluence/transforms/plantuml.py +137 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_abbrevs.py +48 -4
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_emitter.py +18 -2
- mkdocs2confluence-0.17.0/tests/test_plantuml.py +270 -0
- mkdocs2confluence-0.16.0/src/mkdocs_to_confluence/parser/__init__.py +0 -5
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/LICENSE +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/setup.cfg +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/cli.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/compiler/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/compiler/models.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/ir/document.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/loader/config.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/loader/nav.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/loader/page.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/pdf/render.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preprocess/captions.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preview/render.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/preview/server.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/publisher/changelog.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/publisher/client.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/publisher/executor.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/publisher/http_retry.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/publisher/models.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/publisher/pipeline.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/publisher/planner.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/skill_installer.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/skills/mkdocs-changelog/SKILL.md +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/skills/mkdocs-changelog/scripts/changelog_data.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/sync/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/sync/anchoring.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/sync/command.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/sync/comments.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/sync/github.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/sync/platform.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/sync/state.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/admonition_titles.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/attachment_previews.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/captions.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/footer.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/images.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_admonition_titles.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_attachment_previews.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_captions.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_changelog_config.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_changelog_publish.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_children_macro.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_cli.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_editlink.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_exclude_properties_config.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_extra_css.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_footer.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_frontmatter.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_icons.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_images.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_internallinks.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_ir.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_linkdefs.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_loader.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_mermaid.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_page_loader.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_parser.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_pdf.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_preprocess.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_preview.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_publish_client.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_publish_config.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_publish_pipeline.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_server.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_skill_installer.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_sync_anchoring.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_sync_command.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_sync_comments.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_sync_github.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_sync_state.py +0 -0
- {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/tests/test_treeutil.py +0 -0
{mkdocs2confluence-0.16.0/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.17.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
|
|
5
5
|
Author: Anders Hybertz
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -158,7 +158,7 @@ confluence:
|
|
|
158
158
|
email: user@example.com
|
|
159
159
|
token: !ENV CONFLUENCE_API_TOKEN # never hardcode the token
|
|
160
160
|
parent_page_id: "123456" # optional root page
|
|
161
|
-
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
|
|
161
|
+
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none" — also controls PlantUML rendering
|
|
162
162
|
full_width: true # default: true
|
|
163
163
|
attachment_preview: false # default: false — inline PDF/Office previews
|
|
164
164
|
changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
|
|
@@ -116,7 +116,7 @@ confluence:
|
|
|
116
116
|
email: user@example.com
|
|
117
117
|
token: !ENV CONFLUENCE_API_TOKEN # never hardcode the token
|
|
118
118
|
parent_page_id: "123456" # optional root page
|
|
119
|
-
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
|
|
119
|
+
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none" — also controls PlantUML rendering
|
|
120
120
|
full_width: true # default: true
|
|
121
121
|
attachment_preview: false # default: false — inline PDF/Office previews
|
|
122
122
|
changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mkdocs2confluence"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.17.0"
|
|
4
4
|
description = "Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "GPL-3.0-or-later" }
|
{mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0/src/mkdocs2confluence.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
|
|
5
5
|
Author: Anders Hybertz
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -158,7 +158,7 @@ confluence:
|
|
|
158
158
|
email: user@example.com
|
|
159
159
|
token: !ENV CONFLUENCE_API_TOKEN # never hardcode the token
|
|
160
160
|
parent_page_id: "123456" # optional root page
|
|
161
|
-
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
|
|
161
|
+
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none" — also controls PlantUML rendering
|
|
162
162
|
full_width: true # default: true
|
|
163
163
|
attachment_preview: false # default: false — inline PDF/Office previews
|
|
164
164
|
changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
|
{mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs2confluence.egg-info/SOURCES.txt
RENAMED
|
@@ -58,6 +58,7 @@ src/mkdocs_to_confluence/sync/github.py
|
|
|
58
58
|
src/mkdocs_to_confluence/sync/platform.py
|
|
59
59
|
src/mkdocs_to_confluence/sync/state.py
|
|
60
60
|
src/mkdocs_to_confluence/transforms/__init__.py
|
|
61
|
+
src/mkdocs_to_confluence/transforms/_kroki.py
|
|
61
62
|
src/mkdocs_to_confluence/transforms/abbrevs.py
|
|
62
63
|
src/mkdocs_to_confluence/transforms/admonition_titles.py
|
|
63
64
|
src/mkdocs_to_confluence/transforms/assets.py
|
|
@@ -68,6 +69,7 @@ src/mkdocs_to_confluence/transforms/footer.py
|
|
|
68
69
|
src/mkdocs_to_confluence/transforms/images.py
|
|
69
70
|
src/mkdocs_to_confluence/transforms/internallinks.py
|
|
70
71
|
src/mkdocs_to_confluence/transforms/mermaid.py
|
|
72
|
+
src/mkdocs_to_confluence/transforms/plantuml.py
|
|
71
73
|
tests/test_abbrevs.py
|
|
72
74
|
tests/test_admonition_titles.py
|
|
73
75
|
tests/test_attachment_previews.py
|
|
@@ -92,6 +94,7 @@ tests/test_mermaid.py
|
|
|
92
94
|
tests/test_page_loader.py
|
|
93
95
|
tests/test_parser.py
|
|
94
96
|
tests/test_pdf.py
|
|
97
|
+
tests/test_plantuml.py
|
|
95
98
|
tests/test_preprocess.py
|
|
96
99
|
tests/test_preview.py
|
|
97
100
|
tests/test_publish_client.py
|
{mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/compiler/page.py
RENAMED
|
@@ -8,7 +8,7 @@ from mkdocs_to_confluence.ir.nodes import ChildrenMacro, FrontMatter, SourceFoot
|
|
|
8
8
|
from mkdocs_to_confluence.loader.config import MkDocsConfig
|
|
9
9
|
from mkdocs_to_confluence.loader.nav import NavNode
|
|
10
10
|
from mkdocs_to_confluence.loader.page import load_page
|
|
11
|
-
from mkdocs_to_confluence.parser.markdown import parse
|
|
11
|
+
from mkdocs_to_confluence.parser.markdown import parse, parse_inline
|
|
12
12
|
from mkdocs_to_confluence.preprocess.abbrevs import (
|
|
13
13
|
extract_abbreviations,
|
|
14
14
|
strip_abbreviation_defs,
|
|
@@ -26,6 +26,7 @@ from mkdocs_to_confluence.preprocess.linkdefs import (
|
|
|
26
26
|
expand_link_refs,
|
|
27
27
|
strip_link_defs,
|
|
28
28
|
)
|
|
29
|
+
from mkdocs_to_confluence.transforms._kroki import DEFAULT_KROKI_URL
|
|
29
30
|
from mkdocs_to_confluence.transforms.abbrevs import apply_abbreviations
|
|
30
31
|
from mkdocs_to_confluence.transforms.admonition_titles import (
|
|
31
32
|
strip_links_in_admonition_titles,
|
|
@@ -38,7 +39,8 @@ from mkdocs_to_confluence.transforms.captions import resolve_captions
|
|
|
38
39
|
from mkdocs_to_confluence.transforms.editlink import attach_source_url
|
|
39
40
|
from mkdocs_to_confluence.transforms.footer import build_source_footer
|
|
40
41
|
from mkdocs_to_confluence.transforms.internallinks import resolve_internal_links
|
|
41
|
-
from mkdocs_to_confluence.transforms.mermaid import
|
|
42
|
+
from mkdocs_to_confluence.transforms.mermaid import render_mermaid_diagrams
|
|
43
|
+
from mkdocs_to_confluence.transforms.plantuml import render_plantuml_diagrams
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
def compile_page(
|
|
@@ -79,7 +81,9 @@ def compile_page(
|
|
|
79
81
|
ir_nodes = strip_links_in_admonition_titles(ir_nodes, node.docs_path or "")
|
|
80
82
|
if is_section_index:
|
|
81
83
|
ir_nodes = ir_nodes + (ChildrenMacro(),)
|
|
82
|
-
|
|
84
|
+
# Parse definitions as inline markdown so links etc. survive into the glossary.
|
|
85
|
+
parsed_abbrevs = {abbr: parse_inline(defn) for abbr, defn in abbrevs.items()}
|
|
86
|
+
ir_nodes = apply_abbreviations(ir_nodes, parsed_abbrevs, page_text=preprocessed)
|
|
83
87
|
ir_nodes, attachments = resolve_local_assets(
|
|
84
88
|
ir_nodes,
|
|
85
89
|
page_path=node.source_path,
|
|
@@ -97,6 +101,8 @@ def compile_page(
|
|
|
97
101
|
)
|
|
98
102
|
ir_nodes, mermaid_attachments = render_mermaid_diagrams(ir_nodes, kroki_url, quiet=quiet)
|
|
99
103
|
attachments = attachments + mermaid_attachments
|
|
104
|
+
ir_nodes, plantuml_attachments = render_plantuml_diagrams(ir_nodes, kroki_url, quiet=quiet)
|
|
105
|
+
attachments = attachments + plantuml_attachments
|
|
100
106
|
effective_link_map = link_map if link_map is not None else {}
|
|
101
107
|
if node.docs_path:
|
|
102
108
|
ir_nodes = resolve_internal_links(ir_nodes, effective_link_map, node.docs_path)
|
{mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/emitter/xhtml.py
RENAMED
|
@@ -51,6 +51,7 @@ from mkdocs_to_confluence.ir.nodes import (
|
|
|
51
51
|
MermaidDiagram,
|
|
52
52
|
OrderedList,
|
|
53
53
|
Paragraph,
|
|
54
|
+
PlantUMLDiagram,
|
|
54
55
|
RawHTML,
|
|
55
56
|
RawInlineHtml,
|
|
56
57
|
Section,
|
|
@@ -187,6 +188,8 @@ def _emit_node(node: IRNode) -> str:
|
|
|
187
188
|
return _emit_raw_html(node)
|
|
188
189
|
if isinstance(node, MermaidDiagram):
|
|
189
190
|
return _emit_mermaid(node)
|
|
191
|
+
if isinstance(node, PlantUMLDiagram):
|
|
192
|
+
return _emit_plantuml(node)
|
|
190
193
|
if isinstance(node, ContentTabs):
|
|
191
194
|
return _emit_content_tabs(node)
|
|
192
195
|
if isinstance(node, Expandable):
|
|
@@ -505,6 +508,25 @@ def _emit_mermaid(node: MermaidDiagram) -> str:
|
|
|
505
508
|
)
|
|
506
509
|
|
|
507
510
|
|
|
511
|
+
def _emit_plantuml(node: PlantUMLDiagram) -> str:
|
|
512
|
+
if node.attachment_name is not None:
|
|
513
|
+
filename = html.escape(node.attachment_name)
|
|
514
|
+
local_attr = (
|
|
515
|
+
f' data-local-path="{html.escape(str(node.local_path))}"'
|
|
516
|
+
if node.local_path is not None
|
|
517
|
+
else ""
|
|
518
|
+
)
|
|
519
|
+
return f'<ac:image ac:align="center"{local_attr}><ri:attachment ri:filename="{filename}"/></ac:image>\n'
|
|
520
|
+
# Fallback: show source as a code block when rendering was skipped/disabled.
|
|
521
|
+
safe = node.source.replace("]]>", "]]]]><![CDATA[>")
|
|
522
|
+
return (
|
|
523
|
+
'<ac:structured-macro ac:name="code">\n'
|
|
524
|
+
' <ac:parameter ac:name="language">text</ac:parameter>\n'
|
|
525
|
+
f" <ac:plain-text-body><![CDATA[{safe}]]></ac:plain-text-body>\n"
|
|
526
|
+
"</ac:structured-macro>\n"
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
|
|
508
530
|
def _emit_content_tabs(node: ContentTabs) -> str:
|
|
509
531
|
# Degrade to a series of expand macros (one per tab)
|
|
510
532
|
parts: list[str] = []
|
|
@@ -623,11 +645,11 @@ def _emit_abbrev_glossary_block(node: AbbrevGlossaryBlock) -> str:
|
|
|
623
645
|
f"</ac:structured-macro>"
|
|
624
646
|
)
|
|
625
647
|
abbr = html.escape(fn.abbr)
|
|
626
|
-
defn =
|
|
648
|
+
defn = _emit_inlines(fn.definition)
|
|
627
649
|
parts.append(f"<li>{anchor_macro}<strong>{abbr}</strong> — {defn}</li>\n")
|
|
628
|
-
for
|
|
650
|
+
for fn in node.extras:
|
|
629
651
|
# No anchor — these only appeared in headings/titles, no inline superscript links here.
|
|
630
|
-
parts.append(f"<li><strong>{html.escape(abbr)}</strong> — {
|
|
652
|
+
parts.append(f"<li><strong>{html.escape(fn.abbr)}</strong> — {_emit_inlines(fn.definition)}</li>\n")
|
|
631
653
|
parts.append("</ol>\n")
|
|
632
654
|
return "".join(parts)
|
|
633
655
|
|
|
@@ -378,6 +378,20 @@ class MermaidDiagram(IRNode):
|
|
|
378
378
|
local_path: Path | None = None # set by mermaid transform; used by preview renderer
|
|
379
379
|
|
|
380
380
|
|
|
381
|
+
@dataclass(frozen=True)
|
|
382
|
+
class PlantUMLDiagram(IRNode):
|
|
383
|
+
"""A PlantUML diagram (`` ```plantuml `` fenced block).
|
|
384
|
+
|
|
385
|
+
``source`` is the raw PlantUML DSL. When ``attachment_name`` is set the
|
|
386
|
+
emitter renders an ``<ac:image>`` referencing the uploaded PNG; otherwise
|
|
387
|
+
it falls back to a code block showing the raw source.
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
source: str
|
|
391
|
+
attachment_name: str | None = None
|
|
392
|
+
local_path: Path | None = None # set by plantuml transform; used by preview renderer
|
|
393
|
+
|
|
394
|
+
|
|
381
395
|
@dataclass(frozen=True)
|
|
382
396
|
class Tab(IRNode):
|
|
383
397
|
"""A single tab inside a :class:`ContentTabs` group."""
|
|
@@ -497,11 +511,16 @@ class AbbrevFootnoteNode(IRNode):
|
|
|
497
511
|
|
|
498
512
|
The emitter renders ``ABBR<sup>[N]</sup>`` where ``[N]`` links to the
|
|
499
513
|
corresponding entry in the end-of-page :class:`AbbrevGlossaryBlock`.
|
|
514
|
+
|
|
515
|
+
``definition`` holds the definition text parsed as inline IR nodes so
|
|
516
|
+
that links and other inline formatting survive into the glossary.
|
|
500
517
|
"""
|
|
501
518
|
|
|
502
519
|
abbr: str
|
|
503
|
-
definition:
|
|
504
|
-
|
|
520
|
+
definition: tuple[IRNode, ...]
|
|
521
|
+
# 1-based, assigned by the transform in order of first encounter.
|
|
522
|
+
# ``None`` for glossary-only entries (no inline superscript, no anchor).
|
|
523
|
+
number: int | None
|
|
505
524
|
|
|
506
525
|
|
|
507
526
|
@dataclass(frozen=True)
|
|
@@ -509,17 +528,17 @@ class AbbrevGlossaryBlock(IRNode):
|
|
|
509
528
|
"""End-of-page abbreviations reference block.
|
|
510
529
|
|
|
511
530
|
Rendered as a numbered list (with Confluence anchor targets for the
|
|
512
|
-
back-links) followed by
|
|
513
|
-
|
|
531
|
+
back-links) followed by entries for abbreviations that only appeared
|
|
532
|
+
in headings or other non-expandable contexts.
|
|
514
533
|
|
|
515
534
|
Attributes:
|
|
516
535
|
footnoted: Abbreviations annotated inline, ordered by first encounter.
|
|
517
|
-
extras: ``
|
|
536
|
+
extras: Entries (``number=None``) for abbreviations that could
|
|
518
537
|
not be annotated inline, sorted alphabetically.
|
|
519
538
|
"""
|
|
520
539
|
|
|
521
540
|
footnoted: tuple[AbbrevFootnoteNode, ...]
|
|
522
|
-
extras: tuple[
|
|
541
|
+
extras: tuple[AbbrevFootnoteNode, ...]
|
|
523
542
|
|
|
524
543
|
|
|
525
544
|
# ── Footnotes ────────────────────────────────────────────────────────────────
|
{mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/parser/markdown.py
RENAMED
|
@@ -81,6 +81,7 @@ from mkdocs_to_confluence.ir.nodes import (
|
|
|
81
81
|
MermaidDiagram,
|
|
82
82
|
OrderedList,
|
|
83
83
|
Paragraph,
|
|
84
|
+
PlantUMLDiagram,
|
|
84
85
|
RawInlineHtml,
|
|
85
86
|
Section,
|
|
86
87
|
StrikethroughNode,
|
|
@@ -111,6 +112,16 @@ def parse(text: str) -> tuple[IRNode, ...]:
|
|
|
111
112
|
return _build_tree(tokens)
|
|
112
113
|
|
|
113
114
|
|
|
115
|
+
def parse_inline(text: str) -> tuple[IRNode, ...]:
|
|
116
|
+
"""Parse a standalone snippet of inline markdown into IR inline nodes.
|
|
117
|
+
|
|
118
|
+
Used for one-line fragments that live outside the main document flow,
|
|
119
|
+
e.g. abbreviation definitions, where links and other inline formatting
|
|
120
|
+
must still be honoured.
|
|
121
|
+
"""
|
|
122
|
+
return _parse_inline(text)
|
|
123
|
+
|
|
124
|
+
|
|
114
125
|
# ── Internal token types ──────────────────────────────────────────────────────
|
|
115
126
|
|
|
116
127
|
|
|
@@ -1131,6 +1142,8 @@ def _build_tree(
|
|
|
1131
1142
|
elif isinstance(token, _CodeToken):
|
|
1132
1143
|
if token.language and token.language.lower() == "mermaid":
|
|
1133
1144
|
_append_content(MermaidDiagram(source=token.code), stack, root)
|
|
1145
|
+
elif token.language and token.language.lower() == "plantuml":
|
|
1146
|
+
_append_content(PlantUMLDiagram(source=token.code), stack, root)
|
|
1134
1147
|
else:
|
|
1135
1148
|
_append_content(
|
|
1136
1149
|
CodeBlock(
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Generic Kroki diagram rendering utilities shared by all diagram transforms.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- :func:`kroki_post_png` — low-level HTTP POST to Kroki for any diagram type.
|
|
5
|
+
- :func:`render_diagrams` — concurrent deduplication/replacement loop used by
|
|
6
|
+
every diagram-type transform. Each transform supplies its own ``render_fn``
|
|
7
|
+
that handles type-specific caching and retry behaviour.
|
|
8
|
+
|
|
9
|
+
Shared constants (timeouts, retry counts, etc.) are defined here so diagram
|
|
10
|
+
transforms stay in sync without duplication.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import dataclasses
|
|
16
|
+
import sys
|
|
17
|
+
import threading
|
|
18
|
+
import urllib.request
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Protocol, TypeVar, cast
|
|
23
|
+
|
|
24
|
+
from mkdocs_to_confluence.ir.nodes import IRNode, walk
|
|
25
|
+
from mkdocs_to_confluence.ir.treeutil import replace_nodes
|
|
26
|
+
|
|
27
|
+
DEFAULT_KROKI_URL = "https://kroki.io"
|
|
28
|
+
|
|
29
|
+
_TIMEOUT = 30 # seconds
|
|
30
|
+
_MIN_PNG_BYTES = 67 # smallest valid PNG (1×1 px) is 67 bytes
|
|
31
|
+
_CACHE_LOCK = threading.Lock()
|
|
32
|
+
_MAX_WORKERS = 8
|
|
33
|
+
_RETRY_ATTEMPTS = 3
|
|
34
|
+
_RETRY_BACKOFF = 1.0 # seconds; doubles each attempt
|
|
35
|
+
_RETRYABLE_HTTP = {429, 500, 502, 503, 504}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _DiagramNode(Protocol):
|
|
39
|
+
"""Structural protocol for diagram IR nodes (Mermaid, PlantUML, …)."""
|
|
40
|
+
|
|
41
|
+
source: str
|
|
42
|
+
attachment_name: str | None
|
|
43
|
+
local_path: Path | None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_D = TypeVar("_D", bound="_DiagramNode")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def kroki_post_png(source: str, diagram_type: str, kroki_url: str) -> bytes:
|
|
50
|
+
"""POST *source* to Kroki for *diagram_type* and return PNG bytes."""
|
|
51
|
+
url = f"{kroki_url.rstrip('/')}/{diagram_type}/png"
|
|
52
|
+
body = source.encode("utf-8")
|
|
53
|
+
req = urllib.request.Request(
|
|
54
|
+
url,
|
|
55
|
+
data=body,
|
|
56
|
+
headers={
|
|
57
|
+
"Content-Type": "text/plain",
|
|
58
|
+
"Accept": "image/png",
|
|
59
|
+
"User-Agent": "mk2conf/1.0",
|
|
60
|
+
},
|
|
61
|
+
method="POST",
|
|
62
|
+
)
|
|
63
|
+
with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp: # noqa: S310 # nosec B310
|
|
64
|
+
return cast(bytes, resp.read())
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def warn(msg: str) -> None:
|
|
68
|
+
print(f" warning {msg}", file=sys.stderr)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def render_diagrams(
|
|
72
|
+
nodes: tuple[IRNode, ...],
|
|
73
|
+
node_class: type[Any],
|
|
74
|
+
render_fn: Callable[[str, bool], Path | None],
|
|
75
|
+
*,
|
|
76
|
+
quiet: bool = False,
|
|
77
|
+
) -> tuple[tuple[IRNode, ...], list[Path]]:
|
|
78
|
+
"""Render all *node_class* nodes to PNG concurrently.
|
|
79
|
+
|
|
80
|
+
*render_fn* is called as ``render_fn(source, quiet)`` for each unique
|
|
81
|
+
diagram source; it must return a :class:`Path` on success or ``None`` on
|
|
82
|
+
failure. Each diagram type supplies its own ``render_fn`` that handles
|
|
83
|
+
caching, retries, and any type-specific fallback.
|
|
84
|
+
|
|
85
|
+
Returns the updated IR node tuple (``attachment_name`` set on successful
|
|
86
|
+
renders) and a list of PNG paths to upload as page attachments. Failed
|
|
87
|
+
nodes are left unchanged so the emitter can fall back to a code block.
|
|
88
|
+
"""
|
|
89
|
+
diagrams: list[Any] = []
|
|
90
|
+
seen_sources: set[str] = set()
|
|
91
|
+
for top_node in nodes:
|
|
92
|
+
for node in walk(top_node):
|
|
93
|
+
if isinstance(node, node_class) and node.attachment_name is None:
|
|
94
|
+
if node.source not in seen_sources:
|
|
95
|
+
diagrams.append(node)
|
|
96
|
+
seen_sources.add(node.source)
|
|
97
|
+
|
|
98
|
+
if not diagrams:
|
|
99
|
+
return nodes, []
|
|
100
|
+
|
|
101
|
+
source_to_path: dict[str, Path | None] = {}
|
|
102
|
+
with ThreadPoolExecutor(max_workers=min(_MAX_WORKERS, len(diagrams))) as pool:
|
|
103
|
+
future_to_source = {
|
|
104
|
+
pool.submit(render_fn, d.source, quiet): d.source
|
|
105
|
+
for d in diagrams
|
|
106
|
+
}
|
|
107
|
+
for future in as_completed(future_to_source):
|
|
108
|
+
source = future_to_source[future]
|
|
109
|
+
source_to_path[source] = future.result()
|
|
110
|
+
|
|
111
|
+
attachments: list[Path] = []
|
|
112
|
+
replacements: dict[int, IRNode] = {}
|
|
113
|
+
seen_paths: set[Path] = set()
|
|
114
|
+
|
|
115
|
+
for top_node in nodes:
|
|
116
|
+
for node in walk(top_node):
|
|
117
|
+
if not isinstance(node, node_class) or node.attachment_name is not None:
|
|
118
|
+
continue
|
|
119
|
+
path = source_to_path.get(node.source)
|
|
120
|
+
if path is None:
|
|
121
|
+
continue
|
|
122
|
+
if path not in seen_paths:
|
|
123
|
+
attachments.append(path)
|
|
124
|
+
seen_paths.add(path)
|
|
125
|
+
replacements[id(node)] = dataclasses.replace(node, attachment_name=path.name, local_path=path)
|
|
126
|
+
|
|
127
|
+
if not replacements:
|
|
128
|
+
return nodes, attachments
|
|
129
|
+
|
|
130
|
+
return replace_nodes(nodes, replacements), attachments
|
|
131
|
+
|
|
132
|
+
|
{mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/abbrevs.py
RENAMED
|
@@ -65,7 +65,7 @@ _BLOCK_TYPES = (
|
|
|
65
65
|
class _State:
|
|
66
66
|
"""Mutable transform state threaded through the recursive walk."""
|
|
67
67
|
|
|
68
|
-
def __init__(self, abbrevs: dict[str,
|
|
68
|
+
def __init__(self, abbrevs: dict[str, tuple[IRNode, ...]]) -> None:
|
|
69
69
|
self.abbrevs = abbrevs
|
|
70
70
|
self._expanded_list: list[str] = [] # ordered by first encounter
|
|
71
71
|
self._expanded_set: set[str] = set() # fast membership test
|
|
@@ -207,7 +207,7 @@ def _transform_inline(node: IRNode, state: _State, safe: bool) -> tuple[IRNode,
|
|
|
207
207
|
# ── Glossary builder ──────────────────────────────────────────────────────────
|
|
208
208
|
|
|
209
209
|
|
|
210
|
-
def _find_mentioned(text: str, abbrevs: dict[str,
|
|
210
|
+
def _find_mentioned(text: str, abbrevs: dict[str, tuple[IRNode, ...]]) -> set[str]:
|
|
211
211
|
"""Return abbreviations that appear as whole words anywhere in *text*."""
|
|
212
212
|
return {
|
|
213
213
|
abbr
|
|
@@ -221,7 +221,7 @@ def _find_mentioned(text: str, abbrevs: dict[str, str]) -> set[str]:
|
|
|
221
221
|
|
|
222
222
|
def apply_abbreviations(
|
|
223
223
|
nodes: tuple[IRNode, ...],
|
|
224
|
-
abbrevs: dict[str,
|
|
224
|
+
abbrevs: dict[str, tuple[IRNode, ...]],
|
|
225
225
|
*,
|
|
226
226
|
page_text: str = "",
|
|
227
227
|
) -> tuple[IRNode, ...]:
|
|
@@ -229,8 +229,12 @@ def apply_abbreviations(
|
|
|
229
229
|
|
|
230
230
|
Args:
|
|
231
231
|
nodes: Top-level IR nodes returned by :func:`parse`.
|
|
232
|
-
abbrevs: ``{abbreviation: definition}`` mapping
|
|
233
|
-
|
|
232
|
+
abbrevs: ``{abbreviation: definition}`` mapping where each
|
|
233
|
+
definition is parsed inline IR nodes (the caller parses
|
|
234
|
+
the raw definition strings from
|
|
235
|
+
:func:`~mkdocs_to_confluence.preprocess.abbrevs.extract_abbreviations`
|
|
236
|
+
with :func:`~mkdocs_to_confluence.parser.parse_inline` so
|
|
237
|
+
links and other inline formatting are preserved).
|
|
234
238
|
page_text: The preprocessed page text (after stripping abbreviation
|
|
235
239
|
definition lines) used to detect which abbreviations are
|
|
236
240
|
actually present on the page.
|
|
@@ -255,7 +259,7 @@ def apply_abbreviations(
|
|
|
255
259
|
for i, abbr in enumerate(state._expanded_list)
|
|
256
260
|
)
|
|
257
261
|
extras = tuple(
|
|
258
|
-
(abbr, abbrevs[abbr])
|
|
262
|
+
AbbrevFootnoteNode(abbr=abbr, definition=abbrevs[abbr], number=None)
|
|
259
263
|
for abbr in sorted(mentioned - state._expanded_set)
|
|
260
264
|
)
|
|
261
265
|
|
{mkdocs2confluence-0.16.0 → mkdocs2confluence-0.17.0}/src/mkdocs_to_confluence/transforms/mermaid.py
RENAMED
|
@@ -23,32 +23,30 @@ out to external services.
|
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
25
25
|
import base64
|
|
26
|
-
import dataclasses
|
|
27
26
|
import hashlib
|
|
28
27
|
import json
|
|
29
28
|
import sys
|
|
30
|
-
import threading
|
|
31
29
|
import time
|
|
32
30
|
import urllib.error
|
|
33
31
|
import urllib.request
|
|
34
32
|
import zlib
|
|
35
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
36
33
|
from pathlib import Path
|
|
37
34
|
from typing import cast
|
|
38
35
|
|
|
39
|
-
from mkdocs_to_confluence.ir.nodes import IRNode, MermaidDiagram
|
|
40
|
-
from mkdocs_to_confluence.
|
|
36
|
+
from mkdocs_to_confluence.ir.nodes import IRNode, MermaidDiagram
|
|
37
|
+
from mkdocs_to_confluence.transforms._kroki import (
|
|
38
|
+
_CACHE_LOCK,
|
|
39
|
+
_MIN_PNG_BYTES,
|
|
40
|
+
_RETRY_ATTEMPTS,
|
|
41
|
+
_RETRY_BACKOFF,
|
|
42
|
+
_RETRYABLE_HTTP,
|
|
43
|
+
_TIMEOUT,
|
|
44
|
+
DEFAULT_KROKI_URL,
|
|
45
|
+
render_diagrams,
|
|
46
|
+
)
|
|
41
47
|
|
|
42
48
|
_CACHE_DIR = Path.home() / ".cache" / "mk2conf" / "mermaid"
|
|
43
|
-
DEFAULT_KROKI_URL = "https://kroki.io"
|
|
44
49
|
_MERMAID_INK_URL = "https://mermaid.ink"
|
|
45
|
-
_TIMEOUT = 30 # seconds
|
|
46
|
-
_MIN_PNG_BYTES = 67 # smallest valid PNG (1×1 px) is 67 bytes
|
|
47
|
-
_CACHE_LOCK = threading.Lock()
|
|
48
|
-
_MAX_WORKERS = 8
|
|
49
|
-
_RETRY_ATTEMPTS = 3
|
|
50
|
-
_RETRY_BACKOFF = 1.0 # seconds; doubles each attempt
|
|
51
|
-
_RETRYABLE_HTTP = {429, 500, 502, 503, 504}
|
|
52
50
|
|
|
53
51
|
|
|
54
52
|
def _kroki_png(source: str, kroki_url: str) -> bytes:
|
|
@@ -189,49 +187,7 @@ def render_mermaid_diagrams(
|
|
|
189
187
|
except OSError as exc:
|
|
190
188
|
_warn(f"cannot create mermaid cache dir {_CACHE_DIR}: {exc} — all diagrams will fall back to code blocks")
|
|
191
189
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
for node in walk(top_node):
|
|
197
|
-
if isinstance(node, MermaidDiagram) and node.attachment_name is None:
|
|
198
|
-
if node.source not in seen_sources:
|
|
199
|
-
diagrams.append(node)
|
|
200
|
-
seen_sources.add(node.source)
|
|
201
|
-
|
|
202
|
-
if not diagrams:
|
|
203
|
-
return nodes, []
|
|
204
|
-
|
|
205
|
-
# Render all diagrams concurrently, preserving source → result mapping.
|
|
206
|
-
source_to_path: dict[str, Path | None] = {}
|
|
207
|
-
with ThreadPoolExecutor(max_workers=min(_MAX_WORKERS, len(diagrams))) as pool:
|
|
208
|
-
future_to_source = {
|
|
209
|
-
pool.submit(_render_one, d.source, kroki_url, quiet=quiet): d.source for d in diagrams
|
|
210
|
-
}
|
|
211
|
-
for future in as_completed(future_to_source):
|
|
212
|
-
source = future_to_source[future]
|
|
213
|
-
source_to_path[source] = future.result()
|
|
214
|
-
|
|
215
|
-
# Build replacements and attachments from results.
|
|
216
|
-
attachments: list[Path] = []
|
|
217
|
-
replacements: dict[int, IRNode] = {}
|
|
218
|
-
seen_paths: set[Path] = set()
|
|
219
|
-
|
|
220
|
-
for top_node in nodes:
|
|
221
|
-
for node in walk(top_node):
|
|
222
|
-
if not isinstance(node, MermaidDiagram) or node.attachment_name is not None:
|
|
223
|
-
continue
|
|
224
|
-
path = source_to_path.get(node.source)
|
|
225
|
-
if path is None:
|
|
226
|
-
continue # render failed — leave as code block
|
|
227
|
-
if path not in seen_paths:
|
|
228
|
-
attachments.append(path)
|
|
229
|
-
seen_paths.add(path)
|
|
230
|
-
replacements[id(node)] = dataclasses.replace(
|
|
231
|
-
node, attachment_name=path.name, local_path=path
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
if not replacements:
|
|
235
|
-
return nodes, attachments
|
|
236
|
-
|
|
237
|
-
return replace_nodes(nodes, replacements), attachments
|
|
190
|
+
def render_fn(source: str, q: bool) -> Path | None:
|
|
191
|
+
return _render_one(source, kroki_url, quiet=q)
|
|
192
|
+
|
|
193
|
+
return render_diagrams(nodes, MermaidDiagram, render_fn, quiet=quiet)
|