mkdocs2confluence 0.9.3__tar.gz → 0.9.5__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.9.3 → mkdocs2confluence-0.9.5}/PKG-INFO +6 -1
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/README.md +5 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/pyproject.toml +1 -1
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/PKG-INFO +6 -1
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/mermaid.py +57 -3
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_mermaid.py +59 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/LICENSE +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/setup.cfg +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/SOURCES.txt +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/cli.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/emitter/xhtml.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/ir/document.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/ir/nodes.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/loader/config.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/loader/nav.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/loader/page.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/parser/markdown.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/pdf/render.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preview/render.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preview/server.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/publisher/client.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/publisher/pipeline.py +2 -2
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/anchoring.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/command.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/comments.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/github.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/platform.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/state.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/footer.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/images.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_abbrevs.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_children_macro.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_cli.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_editlink.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_emitter.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_extra_css.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_footer.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_frontmatter.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_icons.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_images.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_internallinks.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_ir.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_linkdefs.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_loader.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_page_loader.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_parser.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_pdf.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_preprocess.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_preview.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_publish_client.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_publish_config.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_publish_pipeline.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_server.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_sync_anchoring.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_sync_command.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_sync_comments.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_sync_github.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_sync_state.py +0 -0
- {mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/tests/test_treeutil.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5
|
|
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
|
|
@@ -245,6 +245,8 @@ mk2conf publish [--config PATH] [--page PATH] [--section SECTION] [--dry-run] [-
|
|
|
245
245
|
|
|
246
246
|
If Kroki is unreachable the run continues, falling back to the `code` macro for affected diagrams.
|
|
247
247
|
|
|
248
|
+
**Automatic mermaid.ink fallback:** when using the public `kroki.io` service and a Mermaid diagram receives a 504 (gateway timeout), mk2conf automatically retries that diagram via [mermaid.ink](https://mermaid.ink) before giving up. No configuration needed — the fallback is transparent and only applies to Mermaid diagrams on the public service. Self-hosted Kroki instances never contact mermaid.ink, preserving network isolation.
|
|
249
|
+
|
|
248
250
|
#### Styling from extra.css
|
|
249
251
|
|
|
250
252
|
If `mkdocs.yml` lists `extra_css:` files, mk2conf reads them and applies a whitelisted set of CSS properties as inline `style="..."` attributes on Confluence output.
|
|
@@ -368,6 +370,7 @@ confluence:
|
|
|
368
370
|
| `^^inserted^^` | `<u>` (pymdownx.caret insert) |
|
|
369
371
|
| `` `inline code` `` | `<code>` |
|
|
370
372
|
| `[text](url)` | `<a href="...">` |
|
|
373
|
+
| `[text][label]` / `[text][]` with `[label]: url` | Resolved to inline link before parsing |
|
|
371
374
|
| `https://bare-url` | `<a href="...">` (autolink) |
|
|
372
375
|
| `[text](file.pdf)` | `<ac:link><ri:attachment .../>` (uploaded as attachment) |
|
|
373
376
|
| `` | `<ac:image>` with `<ri:attachment>` (local) or `<ri:url>` (remote) |
|
|
@@ -433,6 +436,8 @@ When `repo_url` + `edit_uri` are set in `mkdocs.yml`, a **Page source** footer p
|
|
|
433
436
|
- **View history** — links to the file's commit history (derived automatically for GitHub and GitLab URLs)
|
|
434
437
|
- **Last commit** — short commit SHA (linked to that commit), message, author, and relative date from `git log` at publish time (omitted if git is unavailable or the file is untracked)
|
|
435
438
|
|
|
439
|
+
The commit SHA and message are also written to the **Confluence version history** on every publish (`sha: message`), so the page history in Confluence stays in sync with your git log.
|
|
440
|
+
|
|
436
441
|
### Section index child pages
|
|
437
442
|
|
|
438
443
|
When a MkDocs navigation section has an `index.md`, the published Confluence page automatically includes the native **Children Display macro** below the page content. This renders a live, auto-maintained list of all direct child pages — no manual curation needed. The macro is injected by default on every section index page.
|
|
@@ -205,6 +205,8 @@ mk2conf publish [--config PATH] [--page PATH] [--section SECTION] [--dry-run] [-
|
|
|
205
205
|
|
|
206
206
|
If Kroki is unreachable the run continues, falling back to the `code` macro for affected diagrams.
|
|
207
207
|
|
|
208
|
+
**Automatic mermaid.ink fallback:** when using the public `kroki.io` service and a Mermaid diagram receives a 504 (gateway timeout), mk2conf automatically retries that diagram via [mermaid.ink](https://mermaid.ink) before giving up. No configuration needed — the fallback is transparent and only applies to Mermaid diagrams on the public service. Self-hosted Kroki instances never contact mermaid.ink, preserving network isolation.
|
|
209
|
+
|
|
208
210
|
#### Styling from extra.css
|
|
209
211
|
|
|
210
212
|
If `mkdocs.yml` lists `extra_css:` files, mk2conf reads them and applies a whitelisted set of CSS properties as inline `style="..."` attributes on Confluence output.
|
|
@@ -328,6 +330,7 @@ confluence:
|
|
|
328
330
|
| `^^inserted^^` | `<u>` (pymdownx.caret insert) |
|
|
329
331
|
| `` `inline code` `` | `<code>` |
|
|
330
332
|
| `[text](url)` | `<a href="...">` |
|
|
333
|
+
| `[text][label]` / `[text][]` with `[label]: url` | Resolved to inline link before parsing |
|
|
331
334
|
| `https://bare-url` | `<a href="...">` (autolink) |
|
|
332
335
|
| `[text](file.pdf)` | `<ac:link><ri:attachment .../>` (uploaded as attachment) |
|
|
333
336
|
| `` | `<ac:image>` with `<ri:attachment>` (local) or `<ri:url>` (remote) |
|
|
@@ -393,6 +396,8 @@ When `repo_url` + `edit_uri` are set in `mkdocs.yml`, a **Page source** footer p
|
|
|
393
396
|
- **View history** — links to the file's commit history (derived automatically for GitHub and GitLab URLs)
|
|
394
397
|
- **Last commit** — short commit SHA (linked to that commit), message, author, and relative date from `git log` at publish time (omitted if git is unavailable or the file is untracked)
|
|
395
398
|
|
|
399
|
+
The commit SHA and message are also written to the **Confluence version history** on every publish (`sha: message`), so the page history in Confluence stays in sync with your git log.
|
|
400
|
+
|
|
396
401
|
### Section index child pages
|
|
397
402
|
|
|
398
403
|
When a MkDocs navigation section has an `index.md`, the published Confluence page automatically includes the native **Children Display macro** below the page content. This renders a live, auto-maintained list of all direct child pages — no manual curation needed. The macro is injected by default on every section index page.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mkdocs2confluence"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.5"
|
|
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" }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5
|
|
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
|
|
@@ -245,6 +245,8 @@ mk2conf publish [--config PATH] [--page PATH] [--section SECTION] [--dry-run] [-
|
|
|
245
245
|
|
|
246
246
|
If Kroki is unreachable the run continues, falling back to the `code` macro for affected diagrams.
|
|
247
247
|
|
|
248
|
+
**Automatic mermaid.ink fallback:** when using the public `kroki.io` service and a Mermaid diagram receives a 504 (gateway timeout), mk2conf automatically retries that diagram via [mermaid.ink](https://mermaid.ink) before giving up. No configuration needed — the fallback is transparent and only applies to Mermaid diagrams on the public service. Self-hosted Kroki instances never contact mermaid.ink, preserving network isolation.
|
|
249
|
+
|
|
248
250
|
#### Styling from extra.css
|
|
249
251
|
|
|
250
252
|
If `mkdocs.yml` lists `extra_css:` files, mk2conf reads them and applies a whitelisted set of CSS properties as inline `style="..."` attributes on Confluence output.
|
|
@@ -368,6 +370,7 @@ confluence:
|
|
|
368
370
|
| `^^inserted^^` | `<u>` (pymdownx.caret insert) |
|
|
369
371
|
| `` `inline code` `` | `<code>` |
|
|
370
372
|
| `[text](url)` | `<a href="...">` |
|
|
373
|
+
| `[text][label]` / `[text][]` with `[label]: url` | Resolved to inline link before parsing |
|
|
371
374
|
| `https://bare-url` | `<a href="...">` (autolink) |
|
|
372
375
|
| `[text](file.pdf)` | `<ac:link><ri:attachment .../>` (uploaded as attachment) |
|
|
373
376
|
| `` | `<ac:image>` with `<ri:attachment>` (local) or `<ri:url>` (remote) |
|
|
@@ -433,6 +436,8 @@ When `repo_url` + `edit_uri` are set in `mkdocs.yml`, a **Page source** footer p
|
|
|
433
436
|
- **View history** — links to the file's commit history (derived automatically for GitHub and GitLab URLs)
|
|
434
437
|
- **Last commit** — short commit SHA (linked to that commit), message, author, and relative date from `git log` at publish time (omitted if git is unavailable or the file is untracked)
|
|
435
438
|
|
|
439
|
+
The commit SHA and message are also written to the **Confluence version history** on every publish (`sha: message`), so the page history in Confluence stays in sync with your git log.
|
|
440
|
+
|
|
436
441
|
### Section index child pages
|
|
437
442
|
|
|
438
443
|
When a MkDocs navigation section has an `index.md`, the published Confluence page automatically includes the native **Children Display macro** below the page content. This renders a live, auto-maintained list of all direct child pages — no manual curation needed. The macro is injected by default on every section index page.
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/mermaid.py
RENAMED
|
@@ -10,17 +10,28 @@ of the Mermaid source so unchanged diagrams are never re-fetched.
|
|
|
10
10
|
When Kroki is unavailable (network error, HTTP error, timeout, or bad response)
|
|
11
11
|
each affected diagram is left unchanged so the emitter falls back to a fenced
|
|
12
12
|
code block. The rest of the pipeline continues unaffected.
|
|
13
|
+
|
|
14
|
+
Fallback behaviour
|
|
15
|
+
------------------
|
|
16
|
+
When the public ``https://kroki.io`` service returns a 504 (gateway timeout)
|
|
17
|
+
for a Mermaid diagram, mk2conf automatically retries via ``mermaid.ink`` before
|
|
18
|
+
giving up. This fallback does **not** apply to self-hosted Kroki instances
|
|
19
|
+
(configured as ``kroki:<url>``) — those run in isolation and should not reach
|
|
20
|
+
out to external services.
|
|
13
21
|
"""
|
|
14
22
|
|
|
15
23
|
from __future__ import annotations
|
|
16
24
|
|
|
25
|
+
import base64
|
|
17
26
|
import dataclasses
|
|
18
27
|
import hashlib
|
|
28
|
+
import json
|
|
19
29
|
import sys
|
|
20
30
|
import threading
|
|
21
31
|
import time
|
|
22
32
|
import urllib.error
|
|
23
33
|
import urllib.request
|
|
34
|
+
import zlib
|
|
24
35
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
25
36
|
from pathlib import Path
|
|
26
37
|
from typing import cast
|
|
@@ -30,7 +41,8 @@ from mkdocs_to_confluence.ir.treeutil import replace_nodes
|
|
|
30
41
|
|
|
31
42
|
_CACHE_DIR = Path.home() / ".cache" / "mk2conf" / "mermaid"
|
|
32
43
|
DEFAULT_KROKI_URL = "https://kroki.io"
|
|
33
|
-
|
|
44
|
+
_MERMAID_INK_URL = "https://mermaid.ink"
|
|
45
|
+
_TIMEOUT = 30 # seconds
|
|
34
46
|
_MIN_PNG_BYTES = 67 # smallest valid PNG (1×1 px) is 67 bytes
|
|
35
47
|
_CACHE_LOCK = threading.Lock()
|
|
36
48
|
_MAX_WORKERS = 8
|
|
@@ -57,6 +69,21 @@ def _kroki_png(source: str, kroki_url: str) -> bytes:
|
|
|
57
69
|
return cast(bytes, resp.read())
|
|
58
70
|
|
|
59
71
|
|
|
72
|
+
def _mermaid_ink_png(source: str) -> bytes:
|
|
73
|
+
"""Fetch a PNG rendering of *source* from mermaid.ink (GET, pako-encoded).
|
|
74
|
+
|
|
75
|
+
Encodes the diagram as ``{"code": source}`` compressed with zlib and
|
|
76
|
+
base64url-encoded — the pako format that mermaid.ink expects.
|
|
77
|
+
"""
|
|
78
|
+
payload = json.dumps({"code": source, "mermaid": {"theme": "default"}}, separators=(",", ":"))
|
|
79
|
+
compressed = zlib.compress(payload.encode("utf-8"))
|
|
80
|
+
encoded = base64.urlsafe_b64encode(compressed).decode().rstrip("=")
|
|
81
|
+
url = f"{_MERMAID_INK_URL}/img/pako:{encoded}?type=png"
|
|
82
|
+
req = urllib.request.Request(url, headers={"User-Agent": "mk2conf/1.0"})
|
|
83
|
+
with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp: # noqa: S310 # nosec B310
|
|
84
|
+
return cast(bytes, resp.read())
|
|
85
|
+
|
|
86
|
+
|
|
60
87
|
def _cache_path(source: str) -> Path:
|
|
61
88
|
digest = hashlib.sha256(source.encode()).hexdigest()
|
|
62
89
|
return _CACHE_DIR / f"mermaid_{digest}.png"
|
|
@@ -71,6 +98,10 @@ def _render_one(source: str, kroki_url: str, *, quiet: bool = False) -> Path | N
|
|
|
71
98
|
|
|
72
99
|
Transient HTTP errors (429, 5xx) and network blips are retried up to
|
|
73
100
|
``_RETRY_ATTEMPTS`` times with exponential backoff.
|
|
101
|
+
|
|
102
|
+
When using the public kroki.io and all retries are exhausted due to 504s,
|
|
103
|
+
one final attempt is made via mermaid.ink before giving up. Self-hosted
|
|
104
|
+
Kroki instances never fall back to mermaid.ink.
|
|
74
105
|
"""
|
|
75
106
|
path = _cache_path(source)
|
|
76
107
|
if path.exists():
|
|
@@ -78,7 +109,10 @@ def _render_one(source: str, kroki_url: str, *, quiet: bool = False) -> Path | N
|
|
|
78
109
|
print(" rendering mermaid diagram (cached)")
|
|
79
110
|
return path
|
|
80
111
|
|
|
112
|
+
use_public_kroki = kroki_url.rstrip("/") == DEFAULT_KROKI_URL.rstrip("/")
|
|
81
113
|
last_exc: Exception | None = None
|
|
114
|
+
last_was_504 = False
|
|
115
|
+
|
|
82
116
|
for attempt in range(_RETRY_ATTEMPTS):
|
|
83
117
|
if attempt > 0:
|
|
84
118
|
delay = _RETRY_BACKOFF * (2 ** (attempt - 1))
|
|
@@ -96,17 +130,34 @@ def _render_one(source: str, kroki_url: str, *, quiet: bool = False) -> Path | N
|
|
|
96
130
|
except urllib.error.HTTPError as exc:
|
|
97
131
|
if exc.code in _RETRYABLE_HTTP:
|
|
98
132
|
last_exc = exc
|
|
133
|
+
last_was_504 = exc.code == 504
|
|
99
134
|
continue # retry
|
|
100
135
|
_warn(f"mermaid diagram: Kroki returned HTTP {exc.code} {exc.reason} — falling back to code block")
|
|
101
136
|
return None
|
|
102
137
|
except urllib.error.URLError as exc:
|
|
103
138
|
last_exc = exc
|
|
139
|
+
last_was_504 = False
|
|
104
140
|
continue # retry — network blip
|
|
105
141
|
except (OSError, ValueError) as exc:
|
|
106
142
|
_warn(f"mermaid diagram: {exc} — falling back to code block")
|
|
107
143
|
return None
|
|
108
144
|
|
|
109
|
-
# All retries exhausted
|
|
145
|
+
# All Kroki retries exhausted — try mermaid.ink if on public kroki.io and last error was 504.
|
|
146
|
+
if use_public_kroki and last_was_504:
|
|
147
|
+
try:
|
|
148
|
+
_warn("mermaid diagram: kroki.io unavailable (504), trying mermaid.ink as fallback")
|
|
149
|
+
png = _mermaid_ink_png(source)
|
|
150
|
+
if len(png) < _MIN_PNG_BYTES:
|
|
151
|
+
raise ValueError(f"mermaid.ink returned {len(png)} bytes (expected a valid PNG)")
|
|
152
|
+
with _CACHE_LOCK:
|
|
153
|
+
path.write_bytes(png)
|
|
154
|
+
if not quiet:
|
|
155
|
+
print(" rendering mermaid diagram (via mermaid.ink fallback)")
|
|
156
|
+
return path
|
|
157
|
+
except (urllib.error.URLError, urllib.error.HTTPError, OSError, ValueError) as exc:
|
|
158
|
+
_warn(f"mermaid diagram: mermaid.ink fallback also failed ({exc}) — falling back to code block")
|
|
159
|
+
return None
|
|
160
|
+
|
|
110
161
|
_warn(f"mermaid diagram: failed after {_RETRY_ATTEMPTS} attempts ({last_exc}) — falling back to code block")
|
|
111
162
|
return None
|
|
112
163
|
|
|
@@ -125,7 +176,10 @@ def render_mermaid_diagrams(
|
|
|
125
176
|
upload as page attachments.
|
|
126
177
|
|
|
127
178
|
Diagrams that fail to render are left unchanged (code-block fallback).
|
|
128
|
-
|
|
179
|
+
When using public kroki.io and a diagram fails with a 504, one automatic
|
|
180
|
+
retry via mermaid.ink is attempted before falling back to a code block.
|
|
181
|
+
Self-hosted Kroki instances never contact mermaid.ink.
|
|
182
|
+
The pipeline always produces valid output regardless of service availability.
|
|
129
183
|
"""
|
|
130
184
|
try:
|
|
131
185
|
_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
@@ -365,3 +365,62 @@ def test_render_mermaid_cached_quiet_suppresses_stdout(tmp_path, capsys):
|
|
|
365
365
|
assert result is not None
|
|
366
366
|
out, _ = capsys.readouterr()
|
|
367
367
|
assert out == ""
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# ── mermaid.ink fallback ──────────────────────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def test_render_one_504_on_public_kroki_falls_back_to_mermaid_ink(tmp_path, capsys):
|
|
374
|
+
"""Public kroki.io 504 → automatic fallback to mermaid.ink for Mermaid."""
|
|
375
|
+
import urllib.error
|
|
376
|
+
|
|
377
|
+
from mkdocs_to_confluence.transforms.mermaid import _render_one
|
|
378
|
+
|
|
379
|
+
with patch("mkdocs_to_confluence.transforms.mermaid._CACHE_DIR", tmp_path), \
|
|
380
|
+
patch("mkdocs_to_confluence.transforms.mermaid.time.sleep"), \
|
|
381
|
+
patch("mkdocs_to_confluence.transforms.mermaid._kroki_png",
|
|
382
|
+
side_effect=urllib.error.HTTPError("url", 504, "Gateway Timeout", {}, None)), \
|
|
383
|
+
patch("mkdocs_to_confluence.transforms.mermaid._mermaid_ink_png",
|
|
384
|
+
return_value=_FAKE_PNG) as mock_ink:
|
|
385
|
+
result = _render_one(_SAMPLE_SOURCE, "https://kroki.io")
|
|
386
|
+
|
|
387
|
+
assert result is not None
|
|
388
|
+
assert result.exists()
|
|
389
|
+
mock_ink.assert_called_once_with(_SAMPLE_SOURCE)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def test_render_one_504_on_self_hosted_kroki_does_not_fall_back(tmp_path):
|
|
393
|
+
"""Self-hosted Kroki 504 → no mermaid.ink fallback (privacy isolation)."""
|
|
394
|
+
import urllib.error
|
|
395
|
+
|
|
396
|
+
from mkdocs_to_confluence.transforms.mermaid import _render_one
|
|
397
|
+
|
|
398
|
+
with patch("mkdocs_to_confluence.transforms.mermaid._CACHE_DIR", tmp_path), \
|
|
399
|
+
patch("mkdocs_to_confluence.transforms.mermaid.time.sleep"), \
|
|
400
|
+
patch("mkdocs_to_confluence.transforms.mermaid._kroki_png",
|
|
401
|
+
side_effect=urllib.error.HTTPError("url", 504, "Gateway Timeout", {}, None)), \
|
|
402
|
+
patch("mkdocs_to_confluence.transforms.mermaid._mermaid_ink_png",
|
|
403
|
+
return_value=_FAKE_PNG) as mock_ink:
|
|
404
|
+
result = _render_one(_SAMPLE_SOURCE, "https://my-internal-kroki.corp")
|
|
405
|
+
|
|
406
|
+
assert result is None
|
|
407
|
+
mock_ink.assert_not_called()
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def test_render_one_mermaid_ink_fallback_also_fails_returns_none(tmp_path, capsys):
|
|
411
|
+
"""When mermaid.ink fallback also fails, returns None (code-block fallback)."""
|
|
412
|
+
import urllib.error
|
|
413
|
+
|
|
414
|
+
from mkdocs_to_confluence.transforms.mermaid import _render_one
|
|
415
|
+
|
|
416
|
+
with patch("mkdocs_to_confluence.transforms.mermaid._CACHE_DIR", tmp_path), \
|
|
417
|
+
patch("mkdocs_to_confluence.transforms.mermaid.time.sleep"), \
|
|
418
|
+
patch("mkdocs_to_confluence.transforms.mermaid._kroki_png",
|
|
419
|
+
side_effect=urllib.error.HTTPError("url", 504, "Gateway Timeout", {}, None)), \
|
|
420
|
+
patch("mkdocs_to_confluence.transforms.mermaid._mermaid_ink_png",
|
|
421
|
+
side_effect=urllib.error.URLError("connection refused")):
|
|
422
|
+
result = _render_one(_SAMPLE_SOURCE, "https://kroki.io")
|
|
423
|
+
|
|
424
|
+
assert result is None
|
|
425
|
+
_, err = capsys.readouterr()
|
|
426
|
+
assert "mermaid.ink" in err
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/requires.txt
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs2confluence.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/emitter/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/emitter/xhtml.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/loader/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/loader/config.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/loader/extra_css.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/parser/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/parser/markdown.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/pdf/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/pdf/generator.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/abbrevs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/fence.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/icons.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/includes.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preprocess/linkdefs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preview/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preview/render.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/preview/server.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/publisher/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/publisher/client.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/publisher/pipeline.py
RENAMED
|
@@ -169,6 +169,8 @@ def compile_page(
|
|
|
169
169
|
preprocessed = expand_link_refs(preprocessed, link_defs)
|
|
170
170
|
preprocessed = strip_link_defs(preprocessed)
|
|
171
171
|
ir_nodes = parse(preprocessed)
|
|
172
|
+
if is_section_index:
|
|
173
|
+
ir_nodes = ir_nodes + (ChildrenMacro(),)
|
|
172
174
|
ir_nodes = apply_abbreviations(ir_nodes, abbrevs, page_text=preprocessed)
|
|
173
175
|
ir_nodes, attachments = resolve_local_assets(
|
|
174
176
|
ir_nodes,
|
|
@@ -192,8 +194,6 @@ def compile_page(
|
|
|
192
194
|
site_url = config.page_site_url(node.docs_path or "")
|
|
193
195
|
if site_url:
|
|
194
196
|
ir_nodes = attach_source_url(ir_nodes, "", site_url)
|
|
195
|
-
if is_section_index:
|
|
196
|
-
ir_nodes = ir_nodes + (ChildrenMacro(),)
|
|
197
197
|
if edit_url:
|
|
198
198
|
abs_path = str(config.docs_dir / (node.docs_path or ""))
|
|
199
199
|
footer = build_source_footer(edit_url, abs_path)
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/anchoring.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/command.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/comments.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/sync/platform.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/abbrevs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/assets.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/editlink.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/footer.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.9.3 → mkdocs2confluence-0.9.5}/src/mkdocs_to_confluence/transforms/images.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|