markdown-to-confluence 0.4.6__py3-none-any.whl → 0.4.7__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.
md2conf/csf.py CHANGED
@@ -14,6 +14,8 @@ from typing import Callable, TypeVar
14
14
  import lxml.etree as ET
15
15
  from lxml.builder import ElementMaker
16
16
 
17
+ ElementType = ET._Element # pyright: ignore [reportPrivateUsage]
18
+
17
19
  # XML namespaces typically associated with Confluence Storage Format documents
18
20
  _namespaces = {
19
21
  "ac": "http://atlassian.com/content",
@@ -54,7 +56,7 @@ def with_entities(func: Callable[[Path], R]) -> R:
54
56
  return func(dtd_path)
55
57
 
56
58
 
57
- def _elements_from_strings(dtd_path: Path, items: list[str]) -> ET._Element:
59
+ def _elements_from_strings(dtd_path: Path, items: list[str]) -> ElementType:
58
60
  """
59
61
  Creates an XML document tree from XML fragment strings.
60
62
 
@@ -90,7 +92,7 @@ def _elements_from_strings(dtd_path: Path, items: list[str]) -> ET._Element:
90
92
  raise ParseError() from ex
91
93
 
92
94
 
93
- def elements_from_strings(items: list[str]) -> ET._Element:
95
+ def elements_from_strings(items: list[str]) -> ElementType:
94
96
  """
95
97
  Creates a Confluence Storage Format XML document tree from XML fragment strings.
96
98
 
@@ -103,7 +105,7 @@ def elements_from_strings(items: list[str]) -> ET._Element:
103
105
  return with_entities(lambda dtd_path: _elements_from_strings(dtd_path, items))
104
106
 
105
107
 
106
- def elements_from_string(content: str) -> ET._Element:
108
+ def elements_from_string(content: str) -> ElementType:
107
109
  """
108
110
  Creates a Confluence Storage Format XML document tree from an XML string.
109
111
 
@@ -135,7 +137,7 @@ def content_to_string(content: str) -> str:
135
137
  return with_entities(lambda dtd_path: _content_to_string(dtd_path, content))
136
138
 
137
139
 
138
- def elements_to_string(root: ET._Element) -> str:
140
+ def elements_to_string(root: ElementType) -> str:
139
141
  """
140
142
  Converts a Confluence Storage Format element tree into an XML string to push to Confluence REST API.
141
143
 
@@ -151,11 +153,11 @@ def elements_to_string(root: ET._Element) -> str:
151
153
  raise ValueError("expected: Confluence content")
152
154
 
153
155
 
154
- def is_block_like(elem: ET._Element) -> bool:
156
+ def is_block_like(elem: ElementType) -> bool:
155
157
  return elem.tag in ["div", "li", "ol", "p", "pre", "td", "th", "ul"]
156
158
 
157
159
 
158
- def normalize_inline(elem: ET._Element) -> None:
160
+ def normalize_inline(elem: ElementType) -> None:
159
161
  """
160
162
  Ensures that inline elements are direct children of an eligible block element.
161
163
 
@@ -179,7 +181,7 @@ def normalize_inline(elem: ET._Element) -> None:
179
181
  if not is_block_like(elem):
180
182
  raise ValueError(f"expected: block element; got: {elem.tag!s}")
181
183
 
182
- contents: list[ET._Element] = []
184
+ contents: list[ElementType] = []
183
185
 
184
186
  paragraph = HTML.p()
185
187
  contents.append(paragraph)
md2conf/domain.py CHANGED
@@ -33,6 +33,8 @@ class ConfluenceDocumentOptions:
33
33
  :param render_latex: Whether to pre-render LaTeX formulas into PNG/SVG images.
34
34
  :param diagram_output_format: Target image format for diagrams.
35
35
  :param webui_links: When true, convert relative URLs to Confluence Web UI links.
36
+ :param alignment: Alignment for block-level images and formulas.
37
+ :param use_panel: Whether to transform admonitions and alerts into a Confluence custom panel.
36
38
  """
37
39
 
38
40
  ignore_invalid_url: bool = False
@@ -46,3 +48,5 @@ class ConfluenceDocumentOptions:
46
48
  render_latex: bool = False
47
49
  diagram_output_format: Literal["png", "svg"] = "png"
48
50
  webui_links: bool = False
51
+ alignment: Literal["center", "left", "right"] = "center"
52
+ use_panel: bool = False
md2conf/drawio.py CHANGED
@@ -20,6 +20,8 @@ from urllib.parse import unquote_to_bytes
20
20
 
21
21
  import lxml.etree as ET
22
22
 
23
+ ElementType = ET._Element # pyright: ignore [reportPrivateUsage]
24
+
23
25
  LOGGER = logging.getLogger(__name__)
24
26
 
25
27
 
@@ -49,7 +51,7 @@ def inflate(data: bytes) -> bytes:
49
51
  return zlib.decompress(data, -zlib.MAX_WBITS)
50
52
 
51
53
 
52
- def decompress_diagram(xml_data: typing.Union[bytes, str]) -> ET._Element:
54
+ def decompress_diagram(xml_data: typing.Union[bytes, str]) -> ElementType:
53
55
  """
54
56
  Decompresses the text content of the `<diagram>` element in a draw.io XML document.
55
57
 
@@ -129,7 +131,7 @@ def decompress_diagram(xml_data: typing.Union[bytes, str]) -> ET._Element:
129
131
  return root
130
132
 
131
133
 
132
- def extract_xml_from_png(png_data: bytes) -> ET._Element:
134
+ def extract_xml_from_png(png_data: bytes) -> ElementType:
133
135
  """
134
136
  Extracts an editable draw.io diagram from a PNG file.
135
137
 
@@ -190,7 +192,7 @@ def extract_xml_from_png(png_data: bytes) -> ET._Element:
190
192
  raise DrawioError("not a PNG file made with draw.io")
191
193
 
192
194
 
193
- def extract_xml_from_svg(svg_data: bytes) -> ET._Element:
195
+ def extract_xml_from_svg(svg_data: bytes) -> ElementType:
194
196
  """
195
197
  Extracts an editable draw.io diagram from an SVG file.
196
198
 
md2conf/latex.py CHANGED
@@ -40,7 +40,7 @@ else:
40
40
 
41
41
  matplotlib.rcParams["mathtext.fontset"] = "cm" # change font to "Computer Modern"
42
42
 
43
- LATEX_ENABLED = True
43
+ LATEX_ENABLED = True # pyright: ignore[reportConstantRedefinition]
44
44
 
45
45
  def _render_latex(expression: str, f: BinaryIO, *, format: Literal["png", "svg"], dpi: int, font_size: int) -> None:
46
46
  # create a figure with no axis
@@ -219,7 +219,7 @@ def _get_png_dimensions(source_file: BinaryIO) -> tuple[int, int]:
219
219
  if ihdr.name != b"IHDR":
220
220
  raise ValueError(f"expected: IHDR chunk; got: {ihdr.name!r}")
221
221
 
222
- (width, height, bit_depth, color_type, compression, filter, interlace) = unpack(">IIBBBBB", ihdr.data)
222
+ (width, height, bit_depth, color_type, compression, filter, interlace) = unpack(">IIBBBBB", ihdr.data) # pyright: ignore[reportUnusedVariable]
223
223
  return width, height
224
224
 
225
225
 
md2conf/matcher.py CHANGED
@@ -105,10 +105,8 @@ class MatcherOptions:
105
105
  def _entry_name_dir(entry: Union[Entry, os.DirEntry[str]]) -> tuple[str, bool]:
106
106
  if isinstance(entry, Entry):
107
107
  return entry.name, entry.is_dir
108
- elif isinstance(entry, os.DirEntry):
109
- return entry.name, entry.is_dir()
110
108
  else:
111
- raise NotImplementedError("type match not exhaustive")
109
+ return entry.name, entry.is_dir()
112
110
 
113
111
 
114
112
  class Matcher:
md2conf/processor.py CHANGED
@@ -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, node: DocumentNode, page_id: Optional[ConfluencePageID]) -> None:
143
+ def _synchronize_tree(self, root: DocumentNode, root_id: Optional[ConfluencePageID]) -> None:
144
144
  """
145
145
  Creates the cross-reference index and synchronizes the directory tree structure with the Confluence page hierarchy.
146
146
 
md2conf/publisher.py CHANGED
@@ -141,7 +141,7 @@ class SynchronizingProcessor(Processor):
141
141
  title = None
142
142
  if document.title is not None:
143
143
  meta = self.page_metadata.get(path)
144
- if meta is not None and meta.space_key is not None and meta.title != document.title:
144
+ if meta is not None and meta.title != document.title:
145
145
  conflicting_page_id = self.api.page_exists(document.title, space_id=self.api.space_key_to_id(meta.space_key))
146
146
  if conflicting_page_id is None:
147
147
  title = document.title
md2conf/scanner.py CHANGED
@@ -7,9 +7,10 @@ Copyright 2022-2025, Levente Hunyadi
7
7
  """
8
8
 
9
9
  import re
10
+ import typing
10
11
  from dataclasses import dataclass
11
12
  from pathlib import Path
12
- from typing import Any, Optional, TypeVar
13
+ from typing import Any, Literal, Optional, TypeVar
13
14
 
14
15
  import yaml
15
16
  from strong_typing.core import JsonType
@@ -45,7 +46,7 @@ def extract_frontmatter_block(text: str) -> tuple[Optional[str], str]:
45
46
  return extract_value(r"(?ms)\A---$(.+?)^---$", text)
46
47
 
47
48
 
48
- def extract_frontmatter_properties(text: str) -> tuple[Optional[dict[str, Any]], str]:
49
+ def extract_frontmatter_properties(text: str) -> tuple[Optional[dict[str, JsonType]], str]:
49
50
  "Extracts the front-matter from a Markdown document as a dictionary."
50
51
 
51
52
  block, text = extract_frontmatter_block(text)
@@ -54,7 +55,7 @@ def extract_frontmatter_properties(text: str) -> tuple[Optional[dict[str, Any]],
54
55
  if block is not None:
55
56
  data = yaml.safe_load(block)
56
57
  if isinstance(data, dict):
57
- properties = data
58
+ properties = typing.cast(dict[str, JsonType], data)
58
59
 
59
60
  return properties, text
60
61
 
@@ -73,6 +74,7 @@ class DocumentProperties:
73
74
  :param tags: A list of tags (content labels) extracted from front-matter.
74
75
  :param synchronized: True if the document content is parsed and synchronized with Confluence.
75
76
  :param properties: A dictionary of key-value pairs extracted from front-matter to apply as page properties.
77
+ :param alignment: Alignment for block-level images and formulas.
76
78
  """
77
79
 
78
80
  page_id: Optional[str]
@@ -84,6 +86,7 @@ class DocumentProperties:
84
86
  tags: Optional[list[str]]
85
87
  synchronized: Optional[bool]
86
88
  properties: Optional[dict[str, JsonType]]
89
+ alignment: Optional[Literal["center", "left", "right"]]
87
90
 
88
91
 
89
92
  @dataclass
@@ -98,6 +101,7 @@ class ScannedDocument:
98
101
  :param tags: A list of tags (content labels) extracted from front-matter.
99
102
  :param synchronized: True if the document content is parsed and synchronized with Confluence.
100
103
  :param properties: A dictionary of key-value pairs extracted from front-matter to apply as page properties.
104
+ :param alignment: Alignment for block-level images and formulas.
101
105
  :param text: Text that remains after front-matter and inline properties have been extracted.
102
106
  """
103
107
 
@@ -108,6 +112,7 @@ class ScannedDocument:
108
112
  tags: Optional[list[str]]
109
113
  synchronized: Optional[bool]
110
114
  properties: Optional[dict[str, JsonType]]
115
+ alignment: Optional[Literal["center", "left", "right"]]
111
116
  text: str
112
117
 
113
118
 
@@ -134,6 +139,7 @@ class Scanner:
134
139
  tags: Optional[list[str]] = None
135
140
  synchronized: Optional[bool] = None
136
141
  properties: Optional[dict[str, JsonType]] = None
142
+ alignment: Optional[Literal["center", "left", "right"]] = None
137
143
 
138
144
  # extract front-matter
139
145
  data, text = extract_frontmatter_properties(text)
@@ -146,6 +152,7 @@ class Scanner:
146
152
  tags = p.tags
147
153
  synchronized = p.synchronized
148
154
  properties = p.properties
155
+ alignment = p.alignment
149
156
 
150
157
  return ScannedDocument(
151
158
  page_id=page_id,
@@ -155,6 +162,7 @@ class Scanner:
155
162
  tags=tags,
156
163
  synchronized=synchronized,
157
164
  properties=properties,
165
+ alignment=alignment,
158
166
  text=text,
159
167
  )
160
168
 
@@ -193,7 +201,7 @@ class MermaidScanner:
193
201
  ```
194
202
  """
195
203
 
196
- properties, text = extract_frontmatter_properties(content)
204
+ properties, _ = extract_frontmatter_properties(content)
197
205
  if properties is not None:
198
206
  front_matter = _json_to_object(MermaidProperties, properties)
199
207
  config = front_matter.config or MermaidConfigProperties()
md2conf/xml.py CHANGED
@@ -10,8 +10,11 @@ from typing import Iterable, Optional
10
10
 
11
11
  import lxml.etree as ET
12
12
 
13
+ ElementType = ET._Element # pyright: ignore [reportPrivateUsage]
14
+ AttribType = ET._Attrib # pyright: ignore[reportPrivateUsage]
13
15
 
14
- def _attrs_equal_excluding(attrs1: ET._Attrib, attrs2: ET._Attrib, exclude: set[str]) -> bool:
16
+
17
+ def _attrs_equal_excluding(attrs1: AttribType, attrs2: AttribType, exclude: set[str]) -> bool:
15
18
  """
16
19
  Compares two dictionary objects, excluding keys in the skip set.
17
20
 
@@ -47,7 +50,7 @@ class ElementComparator:
47
50
  self.skip_attributes = set(skip_attributes) if skip_attributes else set()
48
51
  self.skip_elements = set(skip_elements) if skip_elements else set()
49
52
 
50
- def is_equal(self, e1: ET._Element, e2: ET._Element) -> bool:
53
+ def is_equal(self, e1: ElementType, e2: ElementType) -> bool:
51
54
  """
52
55
  Recursively check if two XML elements are equal.
53
56
  """
@@ -82,7 +85,7 @@ class ElementComparator:
82
85
 
83
86
 
84
87
  def is_xml_equal(
85
- tree1: ET._Element, tree2: ET._Element, *, skip_attributes: Optional[Iterable[str]] = None, skip_elements: Optional[Iterable[str]] = None
88
+ tree1: ElementType, tree2: ElementType, *, skip_attributes: Optional[Iterable[str]] = None, skip_elements: Optional[Iterable[str]] = None
86
89
  ) -> bool:
87
90
  """
88
91
  Compare two XML documents for equivalence, ignoring leading/trailing whitespace differences and attribute definition order.
@@ -99,13 +102,13 @@ def is_xml_equal(
99
102
  return ElementComparator(skip_attributes=skip_attributes, skip_elements=skip_elements).is_equal(tree1, tree2)
100
103
 
101
104
 
102
- def element_to_text(node: ET._Element) -> str:
105
+ def element_to_text(node: ElementType) -> str:
103
106
  "Returns all text contained in an element as a concatenated string."
104
107
 
105
108
  return "".join(node.itertext()).strip()
106
109
 
107
110
 
108
- def unwrap_substitute(name: str, root: ET._Element) -> None:
111
+ def unwrap_substitute(name: str, root: ElementType) -> None:
109
112
  """
110
113
  Substitutes all occurrences of an element with its contents.
111
114
 
@@ -1,34 +0,0 @@
1
- markdown_to_confluence-0.4.6.dist-info/licenses/LICENSE,sha256=56L-Y0dyZwyVlINRJRz3PNw-ka-oLVaAq-7d8zo6qlc,1077
2
- md2conf/__init__.py,sha256=-uM9--fczADkWeiQ7PSL-iFlkeCxy7lnd2KhxG3yFso,402
3
- md2conf/__main__.py,sha256=LiJ06zVr3wroMSLgkMf65ehraqJZgktvuN66dkBJaOI,10929
4
- md2conf/api.py,sha256=RcltUP8KhrhrUuVg7SqBqp-AvGZymjMXtUxpzRPGGGc,40847
5
- md2conf/collection.py,sha256=EobgMRJgkYloWlY03NZJ52MRC_SGLpTVCHkltDbQyt0,837
6
- md2conf/converter.py,sha256=olL2FSop5TFxMfVXU4zWk1Lj_fwGN0c73F2ZYv8PU6s,63250
7
- md2conf/csf.py,sha256=VlB0rYRZ8u-bNDdzEvNy2XrJSxaL-MW_hVRHxcoBluU,6409
8
- md2conf/domain.py,sha256=NpeGl-I9_rgKYCKKZT1Ygg3nl5U0-jJHYYrzDVpMSGQ,1965
9
- md2conf/drawio.py,sha256=3RJFFzlp5a7SNVNCnwO_HCDfMy0DqYQeXfHWRPInOVE,8527
10
- md2conf/emoticon.py,sha256=P2L5oQvnRXeVifJQ3sJ2Ck-6ptbxumq2vsT-rM0W0Ms,484
11
- md2conf/entities.dtd,sha256=M6NzqL5N7dPs_eUA_6sDsiSLzDaAacrx9LdttiufvYU,30215
12
- md2conf/environment.py,sha256=RC1jY_TKVbOv2bJxXn27Fj4fNWzyoNUQt6ltgUyVQAQ,3987
13
- md2conf/extra.py,sha256=VuMxuOnnC2Qwy6y52ukIxsaYhrZArRqMmRHRE4QZl8g,687
14
- md2conf/latex.py,sha256=yAClNclguPv-xWBMVWbqvYWLbyUHBVufc2aUzwyKHew,7586
15
- md2conf/local.py,sha256=mvp2kA_eo6JUQ_rlM7zDdEFgBPVxMr3VKP_X1nsLjHE,3747
16
- md2conf/markdown.py,sha256=czabU17tUfhSX1JQGiI_TrMrTmtoVThOwFu_To_Oi_w,3176
17
- md2conf/matcher.py,sha256=m5rZjYZSjhKfdeKS8JdPq7cG861Mc6rVZBkrIOZTHGE,6916
18
- md2conf/mermaid.py,sha256=hGrITJVvhHprjQVoezQ1nQeo6a_lqNihF8L-oJ4t5rc,2633
19
- md2conf/metadata.py,sha256=LzZM-oPNnzCULmLhF516tPlV5zZBknccwMHt8Nan-xg,1007
20
- md2conf/processor.py,sha256=62Yr33uNVEb11Q7SQ8m1KJHMoJozBPSzeUJQ0fwHig0,9733
21
- md2conf/publisher.py,sha256=uSyYb9SOQzMkSGKKQqsoOL5S2CfkVIEbXaCx6CY0Suw,8696
22
- md2conf/puppeteer-config.json,sha256=-dMTAN_7kNTGbDlfXzApl0KJpAWna9YKZdwMKbpOb60,159
23
- md2conf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- md2conf/scanner.py,sha256=APtENrcEgbslVfjjUnUyB81QpOmPoJgwfww0G7onNOY,6578
25
- md2conf/text.py,sha256=fHOrUaPXAjE4iRhHqFq-CiI-knpo4wvyHCWp0crewqA,1736
26
- md2conf/toc.py,sha256=hpqqDbFgNJg5-ul8qWjOglI3Am0sbwR-TLwGN5G9Qo0,2447
27
- md2conf/uri.py,sha256=KbLBdRFtZTQTZd8b4j0LtE8Pb68Ly0WkemF4iW-EAB4,1158
28
- md2conf/xml.py,sha256=Ybf3Ctt6EurVvel0eb1KezF33_e_cDpMwlUqHi4kNLE,5411
29
- markdown_to_confluence-0.4.6.dist-info/METADATA,sha256=zgUR1Bz7EnzbcAkZVPVKqL123Y0L4NeWzbhxyq5yVH4,34414
30
- markdown_to_confluence-0.4.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- markdown_to_confluence-0.4.6.dist-info/entry_points.txt,sha256=F1zxa1wtEObtbHS-qp46330WVFLHdMnV2wQ-ZorRmX0,50
32
- markdown_to_confluence-0.4.6.dist-info/top_level.txt,sha256=_FJfl_kHrHNidyjUOuS01ngu_jDsfc-ZjSocNRJnTzU,8
33
- markdown_to_confluence-0.4.6.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
34
- markdown_to_confluence-0.4.6.dist-info/RECORD,,