markdown-to-confluence 0.4.7__py3-none-any.whl → 0.5.0__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.4.7.dist-info → markdown_to_confluence-0.5.0.dist-info}/METADATA +30 -17
- markdown_to_confluence-0.5.0.dist-info/RECORD +35 -0
- md2conf/__init__.py +1 -1
- md2conf/__main__.py +13 -13
- md2conf/api.py +55 -71
- md2conf/collection.py +2 -2
- md2conf/converter.py +43 -35
- md2conf/domain.py +3 -3
- md2conf/drawio.py +1 -1
- md2conf/environment.py +22 -22
- md2conf/latex.py +9 -9
- md2conf/local.py +5 -6
- md2conf/markdown.py +7 -7
- md2conf/matcher.py +5 -5
- md2conf/mermaid.py +3 -3
- md2conf/metadata.py +1 -2
- md2conf/processor.py +12 -12
- md2conf/publisher.py +1 -2
- md2conf/scanner.py +33 -41
- md2conf/serializer.py +52 -0
- md2conf/toc.py +2 -3
- md2conf/xml.py +4 -6
- markdown_to_confluence-0.4.7.dist-info/RECORD +0 -34
- {markdown_to_confluence-0.4.7.dist-info → markdown_to_confluence-0.5.0.dist-info}/WHEEL +0 -0
- {markdown_to_confluence-0.4.7.dist-info → markdown_to_confluence-0.5.0.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.4.7.dist-info → markdown_to_confluence-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {markdown_to_confluence-0.4.7.dist-info → markdown_to_confluence-0.5.0.dist-info}/top_level.txt +0 -0
- {markdown_to_confluence-0.4.7.dist-info → markdown_to_confluence-0.5.0.dist-info}/zip-safe +0 -0
md2conf/converter.py
CHANGED
|
@@ -16,12 +16,11 @@ import uuid
|
|
|
16
16
|
from abc import ABC, abstractmethod
|
|
17
17
|
from dataclasses import dataclass
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import ClassVar, Literal
|
|
19
|
+
from typing import ClassVar, Literal
|
|
20
20
|
from urllib.parse import ParseResult, quote_plus, urlparse
|
|
21
21
|
|
|
22
22
|
import lxml.etree as ET
|
|
23
|
-
from
|
|
24
|
-
from strong_typing.exception import JsonTypeError
|
|
23
|
+
from cattrs import BaseValidationError
|
|
25
24
|
|
|
26
25
|
from . import drawio, mermaid
|
|
27
26
|
from .collection import ConfluencePageCollection
|
|
@@ -35,6 +34,7 @@ from .markdown import markdown_to_html
|
|
|
35
34
|
from .mermaid import MermaidConfigProperties
|
|
36
35
|
from .metadata import ConfluenceSiteMetadata
|
|
37
36
|
from .scanner import MermaidScanner, ScannedDocument, Scanner
|
|
37
|
+
from .serializer import JsonType
|
|
38
38
|
from .toc import TableOfContentsBuilder
|
|
39
39
|
from .uri import is_absolute_url, to_uuid_urn
|
|
40
40
|
from .xml import element_to_text
|
|
@@ -202,7 +202,7 @@ class NodeVisitor(ABC):
|
|
|
202
202
|
self.visit(source)
|
|
203
203
|
|
|
204
204
|
@abstractmethod
|
|
205
|
-
def transform(self, child: ElementType) ->
|
|
205
|
+
def transform(self, child: ElementType) -> ElementType | None: ...
|
|
206
206
|
|
|
207
207
|
|
|
208
208
|
def title_to_identifier(title: str) -> str:
|
|
@@ -273,11 +273,11 @@ class ImageAttributes:
|
|
|
273
273
|
"""
|
|
274
274
|
|
|
275
275
|
context: FormattingContext
|
|
276
|
-
width:
|
|
277
|
-
height:
|
|
278
|
-
alt:
|
|
279
|
-
title:
|
|
280
|
-
caption:
|
|
276
|
+
width: int | None
|
|
277
|
+
height: int | None
|
|
278
|
+
alt: str | None
|
|
279
|
+
title: str | None
|
|
280
|
+
caption: str | None
|
|
281
281
|
alignment: ImageAlignment = ImageAlignment.CENTER
|
|
282
282
|
|
|
283
283
|
def __post_init__(self) -> None:
|
|
@@ -374,13 +374,13 @@ class ConfluenceConverterOptions:
|
|
|
374
374
|
@dataclass
|
|
375
375
|
class ImageData:
|
|
376
376
|
path: Path
|
|
377
|
-
description:
|
|
377
|
+
description: str | None = None
|
|
378
378
|
|
|
379
379
|
|
|
380
380
|
@dataclass
|
|
381
381
|
class EmbeddedFileData:
|
|
382
382
|
data: bytes
|
|
383
|
-
description:
|
|
383
|
+
description: str | None = None
|
|
384
384
|
|
|
385
385
|
|
|
386
386
|
@dataclass
|
|
@@ -405,18 +405,18 @@ class ConfluencePanel:
|
|
|
405
405
|
|
|
406
406
|
|
|
407
407
|
ConfluencePanel.from_class = {
|
|
408
|
-
"attention": ConfluencePanel("❗", "exclamation", "
|
|
409
|
-
"caution": ConfluencePanel("❌", "x", "
|
|
410
|
-
"danger": ConfluencePanel("☠️", "skull_crossbones", "
|
|
411
|
-
"disclaimer": ConfluencePanel("❗", "exclamation", "
|
|
412
|
-
"error": ConfluencePanel("❌", "x", "
|
|
413
|
-
"flag": ConfluencePanel("🚩", "triangular_flag_on_post", "
|
|
414
|
-
"hint": ConfluencePanel("💡", "bulb", "
|
|
415
|
-
"info": ConfluencePanel("ℹ️", "information_source", "
|
|
416
|
-
"note": ConfluencePanel("📝", "pencil", "
|
|
417
|
-
"tip": ConfluencePanel("💡", "bulb", "
|
|
418
|
-
"important": ConfluencePanel("❗", "exclamation", "
|
|
419
|
-
"warning": ConfluencePanel("⚠️", "warning", "
|
|
408
|
+
"attention": ConfluencePanel("❗", "exclamation", "var(--ds-background-accent-gray-subtlest)"), # rST admonition
|
|
409
|
+
"caution": ConfluencePanel("❌", "x", "var(--ds-background-accent-orange-subtlest)"),
|
|
410
|
+
"danger": ConfluencePanel("☠️", "skull_crossbones", "var(--ds-background-accent-red-subtlest)"), # rST admonition
|
|
411
|
+
"disclaimer": ConfluencePanel("❗", "exclamation", "var(--ds-background-accent-gray-subtlest)"), # GitLab
|
|
412
|
+
"error": ConfluencePanel("❌", "x", "var(--ds-background-accent-red-subtlest)"), # rST admonition
|
|
413
|
+
"flag": ConfluencePanel("🚩", "triangular_flag_on_post", "var(--ds-background-accent-orange-subtlest"), # GitLab
|
|
414
|
+
"hint": ConfluencePanel("💡", "bulb", "var(--ds-background-accent-green-subtlest)"), # rST admonition
|
|
415
|
+
"info": ConfluencePanel("ℹ️", "information_source", "var(--ds-background-accent-blue-subtlest)"),
|
|
416
|
+
"note": ConfluencePanel("📝", "pencil", "var(--ds-background-accent-teal-subtlest)"),
|
|
417
|
+
"tip": ConfluencePanel("💡", "bulb", "var(--ds-background-accent-green-subtlest)"),
|
|
418
|
+
"important": ConfluencePanel("❗", "exclamation", "var(--ds-background-accent-purple-subtlest)"),
|
|
419
|
+
"warning": ConfluencePanel("⚠️", "warning", "var(--ds-background-accent-yellow-subtlest)"),
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
|
|
@@ -506,7 +506,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
506
506
|
else:
|
|
507
507
|
raise DocumentError(msg)
|
|
508
508
|
|
|
509
|
-
def _transform_link(self, anchor: ElementType) ->
|
|
509
|
+
def _transform_link(self, anchor: ElementType) -> ElementType | None:
|
|
510
510
|
"""
|
|
511
511
|
Transforms links (HTML anchor `<a>`).
|
|
512
512
|
|
|
@@ -559,7 +559,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
559
559
|
else:
|
|
560
560
|
return self._transform_attachment_link(anchor, absolute_path)
|
|
561
561
|
|
|
562
|
-
def _transform_page_link(self, anchor: ElementType, relative_url: ParseResult, absolute_path: Path) ->
|
|
562
|
+
def _transform_page_link(self, anchor: ElementType, relative_url: ParseResult, absolute_path: Path) -> ElementType | None:
|
|
563
563
|
"""
|
|
564
564
|
Transforms links to other Markdown documents (Confluence pages).
|
|
565
565
|
"""
|
|
@@ -596,7 +596,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
596
596
|
anchor.set("href", transformed_url.geturl())
|
|
597
597
|
return None
|
|
598
598
|
|
|
599
|
-
def _transform_attachment_link(self, anchor: ElementType, absolute_path: Path) ->
|
|
599
|
+
def _transform_attachment_link(self, anchor: ElementType, absolute_path: Path) -> ElementType | None:
|
|
600
600
|
"""
|
|
601
601
|
Transforms links to document binaries such as PDF, DOCX or XLSX.
|
|
602
602
|
"""
|
|
@@ -713,7 +713,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
713
713
|
else:
|
|
714
714
|
raise DocumentError(msg)
|
|
715
715
|
|
|
716
|
-
def _verify_image_path(self, path: Path) ->
|
|
716
|
+
def _verify_image_path(self, path: Path) -> Path | None:
|
|
717
717
|
"Checks whether an image path is safe to use."
|
|
718
718
|
|
|
719
719
|
# resolve relative path into absolute path w.r.t. base dir
|
|
@@ -817,6 +817,14 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
817
817
|
str(attrs.height),
|
|
818
818
|
),
|
|
819
819
|
)
|
|
820
|
+
if attrs.alignment is ImageAlignment.CENTER:
|
|
821
|
+
parameters.append(
|
|
822
|
+
AC_ELEM(
|
|
823
|
+
"parameter",
|
|
824
|
+
{AC_ATTR("name"): "pCenter"},
|
|
825
|
+
str(1),
|
|
826
|
+
),
|
|
827
|
+
)
|
|
820
828
|
|
|
821
829
|
local_id = str(uuid.uuid4())
|
|
822
830
|
macro_id = str(uuid.uuid4())
|
|
@@ -897,12 +905,12 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
897
905
|
AC_ELEM("plain-text-body", ET.CDATA(content)),
|
|
898
906
|
)
|
|
899
907
|
|
|
900
|
-
def _extract_mermaid_config(self, content: str) ->
|
|
908
|
+
def _extract_mermaid_config(self, content: str) -> MermaidConfigProperties | None:
|
|
901
909
|
"""Extract scale from Mermaid YAML front matter configuration."""
|
|
902
910
|
try:
|
|
903
911
|
properties = MermaidScanner().read(content)
|
|
904
912
|
return properties.config
|
|
905
|
-
except
|
|
913
|
+
except BaseValidationError as ex:
|
|
906
914
|
LOGGER.warning("Failed to extract Mermaid properties: %s", ex)
|
|
907
915
|
return None
|
|
908
916
|
|
|
@@ -1550,7 +1558,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
1550
1558
|
return AC_ELEM("task-list", {}, *tasks)
|
|
1551
1559
|
|
|
1552
1560
|
@override
|
|
1553
|
-
def transform(self, child: ElementType) ->
|
|
1561
|
+
def transform(self, child: ElementType) -> ElementType | None:
|
|
1554
1562
|
"""
|
|
1555
1563
|
Transforms an HTML element tree obtained from a Markdown document into a Confluence Storage Format element tree.
|
|
1556
1564
|
"""
|
|
@@ -1650,7 +1658,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
1650
1658
|
# <li>[x] ...</li>
|
|
1651
1659
|
# </ul>
|
|
1652
1660
|
elif child.tag == "ul":
|
|
1653
|
-
if len(child) > 0 and element_text_starts_with_any(
|
|
1661
|
+
if len(child) > 0 and all(element_text_starts_with_any(item, ["[ ]", "[x]", "[X]"]) for item in child):
|
|
1654
1662
|
return self._transform_tasklist(child)
|
|
1655
1663
|
|
|
1656
1664
|
return None
|
|
@@ -1734,9 +1742,9 @@ class ConversionError(RuntimeError):
|
|
|
1734
1742
|
class ConfluenceDocument:
|
|
1735
1743
|
"Encapsulates an element tree for a Confluence document created by parsing a Markdown document."
|
|
1736
1744
|
|
|
1737
|
-
title:
|
|
1738
|
-
labels:
|
|
1739
|
-
properties:
|
|
1745
|
+
title: str | None
|
|
1746
|
+
labels: list[str] | None
|
|
1747
|
+
properties: dict[str, JsonType] | None
|
|
1740
1748
|
|
|
1741
1749
|
links: list[str]
|
|
1742
1750
|
images: list[ImageData]
|
|
@@ -1844,7 +1852,7 @@ class ConfluenceDocument:
|
|
|
1844
1852
|
return elements_to_string(self.root)
|
|
1845
1853
|
|
|
1846
1854
|
|
|
1847
|
-
def attachment_name(ref:
|
|
1855
|
+
def attachment_name(ref: Path | str) -> str:
|
|
1848
1856
|
"""
|
|
1849
1857
|
Safe name for use with attachment uploads.
|
|
1850
1858
|
|
md2conf/domain.py
CHANGED
|
@@ -7,7 +7,7 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from typing import Literal
|
|
10
|
+
from typing import Literal
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@dataclass
|
|
@@ -39,8 +39,8 @@ class ConfluenceDocumentOptions:
|
|
|
39
39
|
|
|
40
40
|
ignore_invalid_url: bool = False
|
|
41
41
|
heading_anchors: bool = False
|
|
42
|
-
generated_by:
|
|
43
|
-
root_page_id:
|
|
42
|
+
generated_by: str | None = "This page has been generated with a tool."
|
|
43
|
+
root_page_id: ConfluencePageID | None = None
|
|
44
44
|
keep_hierarchy: bool = False
|
|
45
45
|
prefer_raster: bool = True
|
|
46
46
|
render_drawio: bool = False
|
md2conf/drawio.py
CHANGED
|
@@ -51,7 +51,7 @@ def inflate(data: bytes) -> bytes:
|
|
|
51
51
|
return zlib.decompress(data, -zlib.MAX_WBITS)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def decompress_diagram(xml_data:
|
|
54
|
+
def decompress_diagram(xml_data: bytes | str) -> ElementType:
|
|
55
55
|
"""
|
|
56
56
|
Decompresses the text content of the `<diagram>` element in a draw.io XML document.
|
|
57
57
|
|
md2conf/environment.py
CHANGED
|
@@ -7,7 +7,7 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import overload
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class ArgumentError(ValueError):
|
|
@@ -27,10 +27,10 @@ def _validate_domain(domain: str) -> str: ...
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@overload
|
|
30
|
-
def _validate_domain(domain:
|
|
30
|
+
def _validate_domain(domain: str | None) -> str | None: ...
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def _validate_domain(domain:
|
|
33
|
+
def _validate_domain(domain: str | None) -> str | None:
|
|
34
34
|
if domain is None:
|
|
35
35
|
return None
|
|
36
36
|
|
|
@@ -45,10 +45,10 @@ def _validate_base_path(base_path: str) -> str: ...
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
@overload
|
|
48
|
-
def _validate_base_path(base_path:
|
|
48
|
+
def _validate_base_path(base_path: str | None) -> str | None: ...
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def _validate_base_path(base_path:
|
|
51
|
+
def _validate_base_path(base_path: str | None) -> str | None:
|
|
52
52
|
if base_path is None:
|
|
53
53
|
return None
|
|
54
54
|
|
|
@@ -61,13 +61,13 @@ def _validate_base_path(base_path: Optional[str]) -> Optional[str]:
|
|
|
61
61
|
class ConfluenceSiteProperties:
|
|
62
62
|
domain: str
|
|
63
63
|
base_path: str
|
|
64
|
-
space_key:
|
|
64
|
+
space_key: str | None
|
|
65
65
|
|
|
66
66
|
def __init__(
|
|
67
67
|
self,
|
|
68
|
-
domain:
|
|
69
|
-
base_path:
|
|
70
|
-
space_key:
|
|
68
|
+
domain: str | None = None,
|
|
69
|
+
base_path: str | None = None,
|
|
70
|
+
space_key: str | None = None,
|
|
71
71
|
) -> None:
|
|
72
72
|
opt_domain = domain or os.getenv("CONFLUENCE_DOMAIN")
|
|
73
73
|
opt_base_path = base_path or os.getenv("CONFLUENCE_PATH")
|
|
@@ -93,24 +93,24 @@ class ConfluenceConnectionProperties:
|
|
|
93
93
|
:param headers: Additional HTTP headers to pass to Confluence REST API calls.
|
|
94
94
|
"""
|
|
95
95
|
|
|
96
|
-
domain:
|
|
97
|
-
base_path:
|
|
98
|
-
space_key:
|
|
99
|
-
api_url:
|
|
100
|
-
user_name:
|
|
96
|
+
domain: str | None
|
|
97
|
+
base_path: str | None
|
|
98
|
+
space_key: str | None
|
|
99
|
+
api_url: str | None
|
|
100
|
+
user_name: str | None
|
|
101
101
|
api_key: str
|
|
102
|
-
headers:
|
|
102
|
+
headers: dict[str, str] | None
|
|
103
103
|
|
|
104
104
|
def __init__(
|
|
105
105
|
self,
|
|
106
106
|
*,
|
|
107
|
-
api_url:
|
|
108
|
-
domain:
|
|
109
|
-
base_path:
|
|
110
|
-
user_name:
|
|
111
|
-
api_key:
|
|
112
|
-
space_key:
|
|
113
|
-
headers:
|
|
107
|
+
api_url: str | None = None,
|
|
108
|
+
domain: str | None = None,
|
|
109
|
+
base_path: str | None = None,
|
|
110
|
+
user_name: str | None = None,
|
|
111
|
+
api_key: str | None = None,
|
|
112
|
+
space_key: str | None = None,
|
|
113
|
+
headers: dict[str, str] | None = None,
|
|
114
114
|
) -> None:
|
|
115
115
|
opt_api_url = api_url or os.getenv("CONFLUENCE_API_URL")
|
|
116
116
|
opt_domain = domain or os.getenv("CONFLUENCE_DOMAIN")
|
md2conf/latex.py
CHANGED
|
@@ -10,7 +10,7 @@ import importlib.util
|
|
|
10
10
|
from io import BytesIO
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from struct import unpack
|
|
13
|
-
from typing import BinaryIO, Iterable, Literal,
|
|
13
|
+
from typing import BinaryIO, Iterable, Literal, overload
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def render_latex(expression: str, *, format: Literal["png", "svg"] = "png", dpi: int = 100, font_size: int = 12) -> bytes:
|
|
@@ -71,10 +71,10 @@ def get_png_dimensions(*, data: bytes) -> tuple[int, int]: ...
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
@overload
|
|
74
|
-
def get_png_dimensions(*, path:
|
|
74
|
+
def get_png_dimensions(*, path: str | Path) -> tuple[int, int]: ...
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
def get_png_dimensions(*, data:
|
|
77
|
+
def get_png_dimensions(*, data: bytes | None = None, path: str | Path | None = None) -> tuple[int, int]:
|
|
78
78
|
"""
|
|
79
79
|
Returns the width and height of a PNG image inspecting its header.
|
|
80
80
|
|
|
@@ -100,20 +100,20 @@ def remove_png_chunks(names: Iterable[str], *, source_data: bytes) -> bytes: ...
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
@overload
|
|
103
|
-
def remove_png_chunks(names: Iterable[str], *, source_path:
|
|
103
|
+
def remove_png_chunks(names: Iterable[str], *, source_path: str | Path) -> bytes: ...
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
@overload
|
|
107
|
-
def remove_png_chunks(names: Iterable[str], *, source_data: bytes, target_path:
|
|
107
|
+
def remove_png_chunks(names: Iterable[str], *, source_data: bytes, target_path: str | Path) -> None: ...
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
@overload
|
|
111
|
-
def remove_png_chunks(names: Iterable[str], *, source_path:
|
|
111
|
+
def remove_png_chunks(names: Iterable[str], *, source_path: str | Path, target_path: str | Path) -> None: ...
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
def remove_png_chunks(
|
|
115
|
-
names: Iterable[str], *, source_data:
|
|
116
|
-
) ->
|
|
115
|
+
names: Iterable[str], *, source_data: bytes | None = None, source_path: str | Path | None = None, target_path: str | Path | None = None
|
|
116
|
+
) -> bytes | None:
|
|
117
117
|
"""
|
|
118
118
|
Rewrites a PNG file by removing chunks with the specified names.
|
|
119
119
|
|
|
@@ -168,7 +168,7 @@ def _read_signature(f: BinaryIO) -> None:
|
|
|
168
168
|
raise ValueError("not a valid PNG file")
|
|
169
169
|
|
|
170
170
|
|
|
171
|
-
def _read_chunk(f: BinaryIO) ->
|
|
171
|
+
def _read_chunk(f: BinaryIO) -> _Chunk | None:
|
|
172
172
|
"Reads and parses a PNG chunk such as `IHDR` or `tEXt`."
|
|
173
173
|
|
|
174
174
|
length_bytes = f.read(4)
|
md2conf/local.py
CHANGED
|
@@ -9,7 +9,6 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Optional
|
|
13
12
|
|
|
14
13
|
from .converter import ConfluenceDocument
|
|
15
14
|
from .domain import ConfluenceDocumentOptions, ConfluencePageID
|
|
@@ -30,7 +29,7 @@ class LocalProcessor(Processor):
|
|
|
30
29
|
options: ConfluenceDocumentOptions,
|
|
31
30
|
site: ConfluenceSiteMetadata,
|
|
32
31
|
*,
|
|
33
|
-
out_dir:
|
|
32
|
+
out_dir: Path | None,
|
|
34
33
|
root_dir: Path,
|
|
35
34
|
) -> None:
|
|
36
35
|
"""
|
|
@@ -46,7 +45,7 @@ class LocalProcessor(Processor):
|
|
|
46
45
|
self.out_dir = out_dir or root_dir
|
|
47
46
|
|
|
48
47
|
@override
|
|
49
|
-
def _synchronize_tree(self, root: DocumentNode, root_id:
|
|
48
|
+
def _synchronize_tree(self, root: DocumentNode, root_id: ConfluencePageID | None) -> None:
|
|
50
49
|
"""
|
|
51
50
|
Creates the cross-reference index.
|
|
52
51
|
|
|
@@ -89,13 +88,13 @@ class LocalProcessor(Processor):
|
|
|
89
88
|
|
|
90
89
|
|
|
91
90
|
class LocalProcessorFactory(ProcessorFactory):
|
|
92
|
-
out_dir:
|
|
91
|
+
out_dir: Path | None
|
|
93
92
|
|
|
94
93
|
def __init__(
|
|
95
94
|
self,
|
|
96
95
|
options: ConfluenceDocumentOptions,
|
|
97
96
|
site: ConfluenceSiteMetadata,
|
|
98
|
-
out_dir:
|
|
97
|
+
out_dir: Path | None = None,
|
|
99
98
|
) -> None:
|
|
100
99
|
super().__init__(options, site)
|
|
101
100
|
self.out_dir = out_dir
|
|
@@ -113,6 +112,6 @@ class LocalConverter(Converter):
|
|
|
113
112
|
self,
|
|
114
113
|
options: ConfluenceDocumentOptions,
|
|
115
114
|
site: ConfluenceSiteMetadata,
|
|
116
|
-
out_dir:
|
|
115
|
+
out_dir: Path | None = None,
|
|
117
116
|
) -> None:
|
|
118
117
|
super().__init__(LocalProcessorFactory(options, site, out_dir))
|
md2conf/markdown.py
CHANGED
|
@@ -7,7 +7,7 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import xml.etree.ElementTree
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
import markdown
|
|
13
13
|
|
|
@@ -15,11 +15,11 @@ import markdown
|
|
|
15
15
|
def _emoji_generator(
|
|
16
16
|
index: str,
|
|
17
17
|
shortname: str,
|
|
18
|
-
alias:
|
|
19
|
-
uc:
|
|
18
|
+
alias: str | None,
|
|
19
|
+
uc: str | None,
|
|
20
20
|
alt: str,
|
|
21
|
-
title:
|
|
22
|
-
category:
|
|
21
|
+
title: str | None,
|
|
22
|
+
category: str | None,
|
|
23
23
|
options: dict[str, Any],
|
|
24
24
|
md: markdown.Markdown,
|
|
25
25
|
) -> xml.etree.ElementTree.Element:
|
|
@@ -46,9 +46,9 @@ def _verbatim_formatter(
|
|
|
46
46
|
css_class: str,
|
|
47
47
|
options: dict[str, Any],
|
|
48
48
|
md: markdown.Markdown,
|
|
49
|
-
classes:
|
|
49
|
+
classes: list[str] | None = None,
|
|
50
50
|
id_value: str = "",
|
|
51
|
-
attrs:
|
|
51
|
+
attrs: dict[str, str] | None = None,
|
|
52
52
|
**kwargs: Any,
|
|
53
53
|
) -> str:
|
|
54
54
|
"""
|
md2conf/matcher.py
CHANGED
|
@@ -10,7 +10,7 @@ import os.path
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from fnmatch import fnmatch
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Iterable,
|
|
13
|
+
from typing import Iterable, final, overload
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass(frozen=True, eq=True)
|
|
@@ -95,14 +95,14 @@ class MatcherOptions:
|
|
|
95
95
|
"""
|
|
96
96
|
|
|
97
97
|
source: str
|
|
98
|
-
extension:
|
|
98
|
+
extension: str | None = None
|
|
99
99
|
|
|
100
100
|
def __post_init__(self) -> None:
|
|
101
101
|
if self.extension is not None and not self.extension.startswith("."):
|
|
102
102
|
self.extension = f".{self.extension}"
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
def _entry_name_dir(entry:
|
|
105
|
+
def _entry_name_dir(entry: Entry | os.DirEntry[str]) -> tuple[str, bool]:
|
|
106
106
|
if isinstance(entry, Entry):
|
|
107
107
|
return entry.name, entry.is_dir
|
|
108
108
|
else:
|
|
@@ -155,7 +155,7 @@ class Matcher:
|
|
|
155
155
|
|
|
156
156
|
...
|
|
157
157
|
|
|
158
|
-
def is_excluded(self, entry:
|
|
158
|
+
def is_excluded(self, entry: Entry | os.DirEntry[str]) -> bool:
|
|
159
159
|
name, is_dir = _entry_name_dir(entry)
|
|
160
160
|
|
|
161
161
|
# skip hidden files and directories
|
|
@@ -192,7 +192,7 @@ class Matcher:
|
|
|
192
192
|
"""
|
|
193
193
|
...
|
|
194
194
|
|
|
195
|
-
def is_included(self, entry:
|
|
195
|
+
def is_included(self, entry: Entry | os.DirEntry[str]) -> bool:
|
|
196
196
|
return not self.is_excluded(entry)
|
|
197
197
|
|
|
198
198
|
def filter(self, entries: Iterable[Entry]) -> list[Entry]:
|
md2conf/mermaid.py
CHANGED
|
@@ -12,7 +12,7 @@ import os.path
|
|
|
12
12
|
import shutil
|
|
13
13
|
import subprocess
|
|
14
14
|
from dataclasses import dataclass
|
|
15
|
-
from typing import Literal
|
|
15
|
+
from typing import Literal
|
|
16
16
|
|
|
17
17
|
LOGGER = logging.getLogger(__name__)
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ class MermaidConfigProperties:
|
|
|
25
25
|
:param scale: Scaling factor for the rendered diagram.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
scale:
|
|
28
|
+
scale: float | None = None
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def is_docker() -> bool:
|
|
@@ -56,7 +56,7 @@ def has_mmdc() -> bool:
|
|
|
56
56
|
return shutil.which(executable) is not None
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def render_diagram(source: str, output_format: Literal["png", "svg"] = "png", config:
|
|
59
|
+
def render_diagram(source: str, output_format: Literal["png", "svg"] = "png", config: MermaidConfigProperties | None = None) -> bytes:
|
|
60
60
|
"Generates a PNG or SVG image from a Mermaid diagram source."
|
|
61
61
|
|
|
62
62
|
if config is None:
|
md2conf/metadata.py
CHANGED
|
@@ -7,7 +7,6 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from typing import Optional
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
@dataclass
|
|
@@ -22,7 +21,7 @@ class ConfluenceSiteMetadata:
|
|
|
22
21
|
|
|
23
22
|
domain: str
|
|
24
23
|
base_path: str
|
|
25
|
-
space_key:
|
|
24
|
+
space_key: str | None
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
@dataclass
|
md2conf/processor.py
CHANGED
|
@@ -11,7 +11,7 @@ import logging
|
|
|
11
11
|
import os
|
|
12
12
|
from abc import abstractmethod
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Iterable
|
|
14
|
+
from typing import Iterable
|
|
15
15
|
|
|
16
16
|
from .collection import ConfluencePageCollection
|
|
17
17
|
from .converter import ConfluenceDocument
|
|
@@ -26,9 +26,9 @@ LOGGER = logging.getLogger(__name__)
|
|
|
26
26
|
|
|
27
27
|
class DocumentNode:
|
|
28
28
|
absolute_path: Path
|
|
29
|
-
page_id:
|
|
30
|
-
space_key:
|
|
31
|
-
title:
|
|
29
|
+
page_id: str | None
|
|
30
|
+
space_key: str | None
|
|
31
|
+
title: str | None
|
|
32
32
|
synchronized: bool
|
|
33
33
|
|
|
34
34
|
_children: list["DocumentNode"]
|
|
@@ -36,9 +36,9 @@ class DocumentNode:
|
|
|
36
36
|
def __init__(
|
|
37
37
|
self,
|
|
38
38
|
absolute_path: Path,
|
|
39
|
-
page_id:
|
|
40
|
-
space_key:
|
|
41
|
-
title:
|
|
39
|
+
page_id: str | None,
|
|
40
|
+
space_key: str | None,
|
|
41
|
+
title: str | None,
|
|
42
42
|
synchronized: bool,
|
|
43
43
|
):
|
|
44
44
|
self.absolute_path = absolute_path
|
|
@@ -140,7 +140,7 @@ class Processor:
|
|
|
140
140
|
self._update_page(page_id, document, path)
|
|
141
141
|
|
|
142
142
|
@abstractmethod
|
|
143
|
-
def _synchronize_tree(self, root: DocumentNode, root_id:
|
|
143
|
+
def _synchronize_tree(self, root: DocumentNode, root_id: ConfluencePageID | None) -> None:
|
|
144
144
|
"""
|
|
145
145
|
Creates the cross-reference index and synchronizes the directory tree structure with the Confluence page hierarchy.
|
|
146
146
|
|
|
@@ -157,7 +157,7 @@ class Processor:
|
|
|
157
157
|
"""
|
|
158
158
|
...
|
|
159
159
|
|
|
160
|
-
def _index_directory(self, local_dir: Path, parent:
|
|
160
|
+
def _index_directory(self, local_dir: Path, parent: DocumentNode | None) -> DocumentNode:
|
|
161
161
|
"""
|
|
162
162
|
Indexes Markdown files in a directory hierarchy recursively.
|
|
163
163
|
"""
|
|
@@ -181,7 +181,7 @@ class Processor:
|
|
|
181
181
|
directories.sort()
|
|
182
182
|
|
|
183
183
|
# make page act as parent node
|
|
184
|
-
parent_doc:
|
|
184
|
+
parent_doc: Path | None = None
|
|
185
185
|
if FileEntry("index.md") in files:
|
|
186
186
|
parent_doc = local_dir / "index.md"
|
|
187
187
|
elif FileEntry("README.md") in files:
|
|
@@ -277,7 +277,7 @@ class Converter:
|
|
|
277
277
|
else:
|
|
278
278
|
raise ArgumentError(f"expected: valid file or directory path; got: {path}")
|
|
279
279
|
|
|
280
|
-
def process_directory(self, local_dir: Path, root_dir:
|
|
280
|
+
def process_directory(self, local_dir: Path, root_dir: Path | None = None) -> None:
|
|
281
281
|
"""
|
|
282
282
|
Recursively scans a directory hierarchy for Markdown files, and processes each, resolving cross-references.
|
|
283
283
|
"""
|
|
@@ -290,7 +290,7 @@ class Converter:
|
|
|
290
290
|
|
|
291
291
|
self.factory.create(root_dir).process_directory(local_dir)
|
|
292
292
|
|
|
293
|
-
def process_page(self, path: Path, root_dir:
|
|
293
|
+
def process_page(self, path: Path, root_dir: Path | None = None) -> None:
|
|
294
294
|
"""
|
|
295
295
|
Processes a single Markdown file.
|
|
296
296
|
"""
|
md2conf/publisher.py
CHANGED
|
@@ -8,7 +8,6 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Optional
|
|
12
11
|
|
|
13
12
|
from .api import ConfluenceContentProperty, ConfluenceLabel, ConfluenceSession, ConfluenceStatus
|
|
14
13
|
from .converter import ConfluenceDocument, attachment_name, get_volatile_attributes, get_volatile_elements
|
|
@@ -43,7 +42,7 @@ class SynchronizingProcessor(Processor):
|
|
|
43
42
|
self.api = api
|
|
44
43
|
|
|
45
44
|
@override
|
|
46
|
-
def _synchronize_tree(self, root: DocumentNode, root_id:
|
|
45
|
+
def _synchronize_tree(self, root: DocumentNode, root_id: ConfluencePageID | None) -> None:
|
|
47
46
|
"""
|
|
48
47
|
Creates the cross-reference index and synchronizes the directory tree structure with the Confluence page hierarchy.
|
|
49
48
|
|