markdown-to-confluence 0.4.2__py3-none-any.whl → 0.4.3__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.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/METADATA +8 -12
- {markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/RECORD +16 -14
- md2conf/__init__.py +1 -1
- md2conf/__main__.py +42 -10
- md2conf/application.py +3 -2
- md2conf/converter.py +32 -163
- md2conf/domain.py +46 -0
- md2conf/drawio.py +49 -0
- md2conf/local.py +9 -4
- md2conf/markdown.py +108 -0
- md2conf/processor.py +2 -1
- {markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/WHEEL +0 -0
- {markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/licenses/LICENSE +0 -0
- {markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/top_level.txt +0 -0
- {markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/zip-safe +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: markdown-to-confluence
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Publish Markdown files to Confluence wiki
|
|
5
5
|
Author-email: Levente Hunyadi <hunyadi@gmail.com>
|
|
6
6
|
Maintainer-email: Levente Hunyadi <hunyadi@gmail.com>
|
|
@@ -58,7 +58,7 @@ This Python package
|
|
|
58
58
|
* Text with **bold**, *italic*, `monospace`, <ins>underline</ins> and ~~strikethrough~~
|
|
59
59
|
* Link to [sections on the same page](#getting-started) or [external locations](http://example.com/)
|
|
60
60
|
* Subscript and superscript (with HTML tags `<sub>` and `<sup>`)
|
|
61
|
-
* Math formulas with LaTeX notation
|
|
61
|
+
* Math formulas with LaTeX notation
|
|
62
62
|
* Emoji
|
|
63
63
|
* Ordered and unordered lists
|
|
64
64
|
* Block quotes
|
|
@@ -69,8 +69,8 @@ This Python package
|
|
|
69
69
|
* [Table of contents](https://docs.gitlab.com/ee/user/markdown.html#table-of-contents)
|
|
70
70
|
* [Admonitions](https://python-markdown.github.io/extensions/admonition/) and alert boxes in [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) and [GitLab](https://docs.gitlab.com/ee/development/documentation/styleguide/#alert-boxes)
|
|
71
71
|
* [Collapsed sections](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections)
|
|
72
|
-
* draw\.io diagrams
|
|
73
|
-
* [Mermaid diagrams](https://mermaid.live/) in code blocks (converted to images)
|
|
72
|
+
* draw\.io diagrams
|
|
73
|
+
* [Mermaid diagrams](https://mermaid.live/) in code blocks (converted to images)
|
|
74
74
|
|
|
75
75
|
Whenever possible, the implementation uses [Confluence REST API v2](https://developer.atlassian.com/cloud/confluence/rest/v2/) to fetch space properties, and get, create or update page content.
|
|
76
76
|
|
|
@@ -84,9 +84,9 @@ pip install markdown-to-confluence
|
|
|
84
84
|
|
|
85
85
|
### Command-line utilities
|
|
86
86
|
|
|
87
|
-
**Optional.** Converting `*.drawio` diagrams
|
|
87
|
+
**Optional.** Converting `*.drawio` diagrams to PNG or SVG images before uploading to Confluence as attachments requires installing [draw.io](https://www.drawio.com/). (Refer to `--render-drawio`.)
|
|
88
88
|
|
|
89
|
-
**Optional.** Converting code blocks of Mermaid diagrams
|
|
89
|
+
**Optional.** Converting code blocks of Mermaid diagrams to PNG or SVG images before uploading to Confluence as attachments requires [mermaid-cli](https://github.com/mermaid-js/mermaid-cli). (Refer to `--render-mermaid`.)
|
|
90
90
|
|
|
91
91
|
```sh
|
|
92
92
|
npm install -g @mermaid-js/mermaid-cli
|
|
@@ -98,9 +98,9 @@ As authors of *md2conf*, we don't endorse or support any particular Confluence m
|
|
|
98
98
|
|
|
99
99
|
**Optional.** Editable draw\.io diagrams require [draw.io Diagrams marketplace app](https://marketplace.atlassian.com/apps/1210933/draw-io-diagrams-uml-bpmn-aws-erd-flowcharts). (Refer to `--no-render-drawio`.)
|
|
100
100
|
|
|
101
|
-
**Optional.** Displaying Mermaid diagrams in Confluence without pre-rendering requires a marketplace app. (Refer to `--no-render-mermaid`.)
|
|
101
|
+
**Optional.** Displaying Mermaid diagrams in Confluence without pre-rendering in the synchronization phase requires a marketplace app. (Refer to `--no-render-mermaid`.)
|
|
102
102
|
|
|
103
|
-
**Optional.** Displaying formulas and equations requires [
|
|
103
|
+
**Optional.** Displaying formulas and equations in Confluence requires [marketplace app](https://marketplace.atlassian.com/apps/1226109/latex-math-for-confluence-math-formula-equations), refer to [LaTeX Math for Confluence - Math Formula & Equations](https://help.narva.net/latex-math-for-confluence/).
|
|
104
104
|
|
|
105
105
|
## Getting started
|
|
106
106
|
|
|
@@ -523,7 +523,3 @@ FROM leventehunyadi/md2conf:latest
|
|
|
523
523
|
|
|
524
524
|
CMD ["-d", "example.atlassian.net", "-u", "levente.hunyadi@instructure.com", "-a", "0123456789abcdef", "-s", "SPACE", "./"]
|
|
525
525
|
```
|
|
526
|
-
|
|
527
|
-
[^math]: Requires installing Confluence plugin [LaTeX Math for Confluence - Math Formula & Equations](https://help.narva.net/latex-math-for-confluence/).
|
|
528
|
-
[^drawio]: Converting draw\.io diagrams to images before uploading to Confluence requires an installation of [draw.io](https://www.drawio.com/). Editable draw\.io diagrams require separate marketplace app.
|
|
529
|
-
[^mermaid]: Converting Mermaid diagrams to images before uploading to Confluence requires [mermaid-cli](https://github.com/mermaid-js/mermaid-cli). Displaying Mermaid diagrams on the fly requires separate marketplace app.
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
markdown_to_confluence-0.4.
|
|
2
|
-
md2conf/__init__.py,sha256=
|
|
3
|
-
md2conf/__main__.py,sha256=
|
|
1
|
+
markdown_to_confluence-0.4.3.dist-info/licenses/LICENSE,sha256=Pv43so2bPfmKhmsrmXFyAvS7M30-1i1tzjz6-dfhyOo,1077
|
|
2
|
+
md2conf/__init__.py,sha256=ZEoZwOt29zT2OnQNpbYW9lO3zJEJ6soXwwjYX9PwNNo,402
|
|
3
|
+
md2conf/__main__.py,sha256=RImfFrO2m9C5iebmBrHKlLjosy_A8AY4O7PK9CmiWSw,11120
|
|
4
4
|
md2conf/api.py,sha256=DbG1udDb9ti4OjqgSW3DSuHwxKNFPVDTkhjnaB1GNMI,37193
|
|
5
|
-
md2conf/application.py,sha256=
|
|
5
|
+
md2conf/application.py,sha256=MsumqUFw1WPo6-57r06Poq4wg2DPd3hQ4jA5qC4Oios,8212
|
|
6
6
|
md2conf/collection.py,sha256=EobgMRJgkYloWlY03NZJ52MRC_SGLpTVCHkltDbQyt0,837
|
|
7
|
-
md2conf/converter.py,sha256=
|
|
8
|
-
md2conf/
|
|
7
|
+
md2conf/converter.py,sha256=mr5UvEhOnM7ZYRIGsgrW85PpxmlpXFjsKYsa8uGFxp0,50475
|
|
8
|
+
md2conf/domain.py,sha256=tA9V0vb5Vo9Nt0eQvwAFARaM9TX88LBVQ73nVvdcaqA,1851
|
|
9
|
+
md2conf/drawio.py,sha256=P_t7Wp7Tg9XkZM2ZchWCWWEdBaU1KgZ_YX9ZlkZo4Dk,8293
|
|
9
10
|
md2conf/emoji.py,sha256=UzDrxqFo59wHmbbJmMNdn0rYFDXbZE4qirOM-_egzXc,2603
|
|
10
11
|
md2conf/entities.dtd,sha256=M6NzqL5N7dPs_eUA_6sDsiSLzDaAacrx9LdttiufvYU,30215
|
|
11
12
|
md2conf/extra.py,sha256=VuMxuOnnC2Qwy6y52ukIxsaYhrZArRqMmRHRE4QZl8g,687
|
|
12
|
-
md2conf/local.py,sha256=
|
|
13
|
+
md2conf/local.py,sha256=Cicfp9SJDJuX0aUWZPWCfWKPfQQWxEbifUsmqwxFjDU,3733
|
|
14
|
+
md2conf/markdown.py,sha256=9BQbYD4GfpBYmx-3N1M36u2nVWY0VJ9UWKye2Jtnmnk,2901
|
|
13
15
|
md2conf/matcher.py,sha256=m5rZjYZSjhKfdeKS8JdPq7cG861Mc6rVZBkrIOZTHGE,6916
|
|
14
16
|
md2conf/mermaid.py,sha256=f0x7ISj-41ZMh4zTAFPhIWwr94SDcsVZUc1NWqmH_G4,2508
|
|
15
17
|
md2conf/metadata.py,sha256=LzZM-oPNnzCULmLhF516tPlV5zZBknccwMHt8Nan-xg,1007
|
|
16
|
-
md2conf/processor.py,sha256=
|
|
18
|
+
md2conf/processor.py,sha256=z2d2KMPEYWaxflOtH2UTwrjzpPU8TtLSEUvor85ez1Q,9732
|
|
17
19
|
md2conf/properties.py,sha256=RC1jY_TKVbOv2bJxXn27Fj4fNWzyoNUQt6ltgUyVQAQ,3987
|
|
18
20
|
md2conf/puppeteer-config.json,sha256=-dMTAN_7kNTGbDlfXzApl0KJpAWna9YKZdwMKbpOb60,159
|
|
19
21
|
md2conf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
22
|
md2conf/scanner.py,sha256=Cyvjab8tBvKgubttQvNagS8nailuTvFBqUGoiX5MNp8,5351
|
|
21
23
|
md2conf/xml.py,sha256=HoKJfF1yRZ3Gk8jTS-kRpOqVs0nQJZyr56l0Fo3y9fs,2193
|
|
22
|
-
markdown_to_confluence-0.4.
|
|
23
|
-
markdown_to_confluence-0.4.
|
|
24
|
-
markdown_to_confluence-0.4.
|
|
25
|
-
markdown_to_confluence-0.4.
|
|
26
|
-
markdown_to_confluence-0.4.
|
|
27
|
-
markdown_to_confluence-0.4.
|
|
24
|
+
markdown_to_confluence-0.4.3.dist-info/METADATA,sha256=sU45yX796M_cZ0ssGMaDtuxb0xwtKaPTktNAy2rczMg,29119
|
|
25
|
+
markdown_to_confluence-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
+
markdown_to_confluence-0.4.3.dist-info/entry_points.txt,sha256=F1zxa1wtEObtbHS-qp46330WVFLHdMnV2wQ-ZorRmX0,50
|
|
27
|
+
markdown_to_confluence-0.4.3.dist-info/top_level.txt,sha256=_FJfl_kHrHNidyjUOuS01ngu_jDsfc-ZjSocNRJnTzU,8
|
|
28
|
+
markdown_to_confluence-0.4.3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
29
|
+
markdown_to_confluence-0.4.3.dist-info/RECORD,,
|
md2conf/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ Parses Markdown files, converts Markdown content into the Confluence Storage For
|
|
|
5
5
|
Confluence API endpoints to upload images and content.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "0.4.
|
|
8
|
+
__version__ = "0.4.3"
|
|
9
9
|
__author__ = "Levente Hunyadi"
|
|
10
10
|
__copyright__ = "Copyright 2022-2025, Levente Hunyadi"
|
|
11
11
|
__license__ = "MIT"
|
md2conf/__main__.py
CHANGED
|
@@ -17,14 +17,9 @@ import typing
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Any, Iterable, Literal, Optional, Sequence, Union
|
|
19
19
|
|
|
20
|
-
import requests
|
|
21
|
-
|
|
22
20
|
from . import __version__
|
|
23
|
-
from .
|
|
24
|
-
from .application import Application
|
|
25
|
-
from .converter import ConfluenceDocumentOptions, ConfluencePageID
|
|
21
|
+
from .domain import ConfluenceDocumentOptions, ConfluencePageID
|
|
26
22
|
from .extra import override
|
|
27
|
-
from .local import LocalConverter
|
|
28
23
|
from .metadata import ConfluenceSiteMetadata
|
|
29
24
|
from .properties import ArgumentError, ConfluenceConnectionProperties, ConfluenceSiteProperties
|
|
30
25
|
|
|
@@ -52,7 +47,7 @@ class Arguments(argparse.Namespace):
|
|
|
52
47
|
|
|
53
48
|
|
|
54
49
|
class KwargsAppendAction(argparse.Action):
|
|
55
|
-
"""Append key-value pairs to a dictionary"""
|
|
50
|
+
"""Append key-value pairs to a dictionary."""
|
|
56
51
|
|
|
57
52
|
@override
|
|
58
53
|
def __call__(
|
|
@@ -72,6 +67,30 @@ class KwargsAppendAction(argparse.Action):
|
|
|
72
67
|
setattr(namespace, self.dest, d)
|
|
73
68
|
|
|
74
69
|
|
|
70
|
+
def unsupported(prefer: str) -> type[argparse.Action]:
|
|
71
|
+
class UnsupportedAction(argparse.Action):
|
|
72
|
+
"""Display an error for unsupported command-line options."""
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
def __call__(
|
|
76
|
+
self,
|
|
77
|
+
parser: argparse.ArgumentParser,
|
|
78
|
+
namespace: argparse.Namespace,
|
|
79
|
+
values: Union[None, str, Sequence[Any]],
|
|
80
|
+
option_string: Optional[str] = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
raise argparse.ArgumentError(
|
|
83
|
+
self,
|
|
84
|
+
f"this command-line option is no longer supported, use `--{prefer}`",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
def __repr__(self) -> str:
|
|
89
|
+
return f"{unsupported.__name__}({repr(prefer)})"
|
|
90
|
+
|
|
91
|
+
return UnsupportedAction
|
|
92
|
+
|
|
93
|
+
|
|
75
94
|
class PositionalOnlyHelpFormatter(argparse.HelpFormatter):
|
|
76
95
|
def _format_usage(
|
|
77
96
|
self,
|
|
@@ -189,12 +208,18 @@ def main() -> None:
|
|
|
189
208
|
help="Inline Mermaid diagram in Confluence page. (Marketplace app required.)",
|
|
190
209
|
)
|
|
191
210
|
parser.add_argument(
|
|
192
|
-
"--
|
|
211
|
+
"--diagram-output-format",
|
|
193
212
|
dest="diagram_output_format",
|
|
194
213
|
choices=["png", "svg"],
|
|
195
214
|
default="png",
|
|
196
215
|
help="Format for rendering Mermaid and draw.io diagrams (default: 'png').",
|
|
197
216
|
)
|
|
217
|
+
parser.add_argument(
|
|
218
|
+
"--render-mermaid-format",
|
|
219
|
+
action=unsupported("diagram-output-format"),
|
|
220
|
+
metavar="FORMAT",
|
|
221
|
+
help="Format for rendering Mermaid diagrams (default: 'png').",
|
|
222
|
+
)
|
|
198
223
|
parser.add_argument(
|
|
199
224
|
"--heading-anchors",
|
|
200
225
|
action="store_true",
|
|
@@ -256,6 +281,8 @@ def main() -> None:
|
|
|
256
281
|
webui_links=args.webui_links,
|
|
257
282
|
)
|
|
258
283
|
if args.local:
|
|
284
|
+
from .local import LocalConverter
|
|
285
|
+
|
|
259
286
|
try:
|
|
260
287
|
site_properties = ConfluenceSiteProperties(
|
|
261
288
|
domain=args.domain,
|
|
@@ -271,6 +298,11 @@ def main() -> None:
|
|
|
271
298
|
)
|
|
272
299
|
LocalConverter(options, site_metadata).process(args.mdpath)
|
|
273
300
|
else:
|
|
301
|
+
from requests import HTTPError, JSONDecodeError
|
|
302
|
+
|
|
303
|
+
from .api import ConfluenceAPI
|
|
304
|
+
from .application import Application
|
|
305
|
+
|
|
274
306
|
try:
|
|
275
307
|
properties = ConfluenceConnectionProperties(
|
|
276
308
|
api_url=args.api_url,
|
|
@@ -289,14 +321,14 @@ def main() -> None:
|
|
|
289
321
|
api,
|
|
290
322
|
options,
|
|
291
323
|
).process(args.mdpath)
|
|
292
|
-
except
|
|
324
|
+
except HTTPError as err:
|
|
293
325
|
logging.error(err)
|
|
294
326
|
|
|
295
327
|
# print details for a response with JSON body
|
|
296
328
|
if err.response is not None:
|
|
297
329
|
try:
|
|
298
330
|
logging.error(err.response.json())
|
|
299
|
-
except
|
|
331
|
+
except JSONDecodeError:
|
|
300
332
|
pass
|
|
301
333
|
|
|
302
334
|
sys.exit(1)
|
md2conf/application.py
CHANGED
|
@@ -11,7 +11,8 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Optional
|
|
12
12
|
|
|
13
13
|
from .api import ConfluenceContentProperty, ConfluenceLabel, ConfluenceSession, ConfluenceStatus
|
|
14
|
-
from .converter import ConfluenceDocument,
|
|
14
|
+
from .converter import ConfluenceDocument, attachment_name, elements_from_string, get_volatile_attributes
|
|
15
|
+
from .domain import ConfluenceDocumentOptions, ConfluencePageID
|
|
15
16
|
from .extra import override, path_relative_to
|
|
16
17
|
from .metadata import ConfluencePageMetadata
|
|
17
18
|
from .processor import Converter, DocumentNode, Processor, ProcessorFactory
|
|
@@ -158,7 +159,7 @@ class SynchronizingProcessor(Processor):
|
|
|
158
159
|
):
|
|
159
160
|
self.api.update_page(page_id.page_id, content, title=title, version=page.version.number + 1)
|
|
160
161
|
else:
|
|
161
|
-
LOGGER.info("Up-to-date page: %s", page_id)
|
|
162
|
+
LOGGER.info("Up-to-date page: %s", page_id.page_id)
|
|
162
163
|
|
|
163
164
|
if document.labels is not None:
|
|
164
165
|
self.api.update_labels(
|
md2conf/converter.py
CHANGED
|
@@ -8,28 +8,27 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
8
8
|
|
|
9
9
|
# mypy: disable-error-code="dict-item"
|
|
10
10
|
|
|
11
|
+
import dataclasses
|
|
11
12
|
import hashlib
|
|
12
13
|
import importlib.resources as resources
|
|
13
14
|
import logging
|
|
14
15
|
import os.path
|
|
15
16
|
import re
|
|
16
17
|
import uuid
|
|
17
|
-
import xml.etree.ElementTree
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Any, Literal, Optional, Union
|
|
21
21
|
from urllib.parse import ParseResult, quote_plus, urlparse, urlunparse
|
|
22
22
|
|
|
23
23
|
import lxml.etree as ET
|
|
24
|
-
import markdown
|
|
25
24
|
from lxml.builder import ElementMaker
|
|
26
25
|
from strong_typing.core import JsonType
|
|
27
26
|
|
|
28
|
-
from
|
|
29
|
-
|
|
27
|
+
from . import drawio, mermaid
|
|
30
28
|
from .collection import ConfluencePageCollection
|
|
29
|
+
from .domain import ConfluenceDocumentOptions, ConfluencePageID
|
|
31
30
|
from .extra import path_relative_to
|
|
32
|
-
from .
|
|
31
|
+
from .markdown import markdown_to_html
|
|
33
32
|
from .metadata import ConfluenceSiteMetadata
|
|
34
33
|
from .properties import PageError
|
|
35
34
|
from .scanner import ScannedDocument, Scanner
|
|
@@ -101,90 +100,6 @@ def encode_title(text: str) -> str:
|
|
|
101
100
|
return quote_plus(text.strip())
|
|
102
101
|
|
|
103
102
|
|
|
104
|
-
def emoji_generator(
|
|
105
|
-
index: str,
|
|
106
|
-
shortname: str,
|
|
107
|
-
alias: Optional[str],
|
|
108
|
-
uc: Optional[str],
|
|
109
|
-
alt: str,
|
|
110
|
-
title: Optional[str],
|
|
111
|
-
category: Optional[str],
|
|
112
|
-
options: dict[str, Any],
|
|
113
|
-
md: markdown.Markdown,
|
|
114
|
-
) -> xml.etree.ElementTree.Element:
|
|
115
|
-
"""
|
|
116
|
-
Custom generator for `pymdownx.emoji`.
|
|
117
|
-
"""
|
|
118
|
-
|
|
119
|
-
name = (alias or shortname).strip(":")
|
|
120
|
-
span = xml.etree.ElementTree.Element("span", {"data-emoji-shortname": name})
|
|
121
|
-
if uc is not None:
|
|
122
|
-
span.attrib["data-emoji-unicode"] = uc
|
|
123
|
-
|
|
124
|
-
# convert series of Unicode code point hexadecimal values into characters
|
|
125
|
-
span.text = "".join(chr(int(item, base=16)) for item in uc.split("-"))
|
|
126
|
-
else:
|
|
127
|
-
span.text = alt
|
|
128
|
-
return span
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def math_formatter(
|
|
132
|
-
source: str,
|
|
133
|
-
language: str,
|
|
134
|
-
css_class: str,
|
|
135
|
-
options: dict[str, Any],
|
|
136
|
-
md: markdown.Markdown,
|
|
137
|
-
classes: Optional[list[str]] = None,
|
|
138
|
-
id_value: str = "",
|
|
139
|
-
attrs: Optional[dict[str, str]] = None,
|
|
140
|
-
**kwargs: Any,
|
|
141
|
-
) -> str:
|
|
142
|
-
"""
|
|
143
|
-
Custom formatter for language `math` in `pymdownx.superfences`.
|
|
144
|
-
"""
|
|
145
|
-
|
|
146
|
-
if classes is None:
|
|
147
|
-
classes = [css_class]
|
|
148
|
-
else:
|
|
149
|
-
classes.insert(0, css_class)
|
|
150
|
-
|
|
151
|
-
html_id = f' id="{id_value}"' if id_value else ""
|
|
152
|
-
html_class = ' class="{}"'.format(" ".join(classes))
|
|
153
|
-
html_attrs = " " + " ".join(f'{k}="{v}"' for k, v in attrs.items()) if attrs else ""
|
|
154
|
-
|
|
155
|
-
return f"<div{html_id}{html_class}{html_attrs}>{source}</div>"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def markdown_to_html(content: str) -> str:
|
|
159
|
-
return markdown.markdown(
|
|
160
|
-
content,
|
|
161
|
-
extensions=[
|
|
162
|
-
"admonition",
|
|
163
|
-
"footnotes",
|
|
164
|
-
"markdown.extensions.tables",
|
|
165
|
-
"md_in_html",
|
|
166
|
-
"pymdownx.arithmatex",
|
|
167
|
-
"pymdownx.emoji",
|
|
168
|
-
"pymdownx.highlight", # required by `pymdownx.superfences`
|
|
169
|
-
"pymdownx.magiclink",
|
|
170
|
-
"pymdownx.superfences",
|
|
171
|
-
"pymdownx.tilde",
|
|
172
|
-
"sane_lists",
|
|
173
|
-
],
|
|
174
|
-
extension_configs={
|
|
175
|
-
"footnotes": {"BACKLINK_TITLE": ""},
|
|
176
|
-
"pymdownx.arithmatex": {"generic": True, "preview": False, "tex_inline_wrap": ["", ""], "tex_block_wrap": ["", ""]},
|
|
177
|
-
"pymdownx.emoji": {
|
|
178
|
-
"emoji_generator": emoji_generator,
|
|
179
|
-
},
|
|
180
|
-
"pymdownx.highlight": {
|
|
181
|
-
"use_pygments": False,
|
|
182
|
-
},
|
|
183
|
-
"pymdownx.superfences": {"custom_fences": [{"name": "math", "class": "arithmatex", "format": math_formatter}]},
|
|
184
|
-
},
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
|
|
188
103
|
def _elements_from_strings(dtd_path: Path, items: list[str]) -> ET._Element:
|
|
189
104
|
"""
|
|
190
105
|
Creates a fragment of several XML nodes from their string representation wrapped in a root element.
|
|
@@ -333,8 +248,8 @@ def title_to_identifier(title: str) -> str:
|
|
|
333
248
|
"Converts a section heading title to a GitHub-style Markdown same-page anchor."
|
|
334
249
|
|
|
335
250
|
s = title.strip().lower()
|
|
336
|
-
s = re.sub("[
|
|
337
|
-
s =
|
|
251
|
+
s = re.sub(r"[^\sA-Za-z0-9_\-]", "", s)
|
|
252
|
+
s = re.sub(r"\s+", "-", s)
|
|
338
253
|
return s
|
|
339
254
|
|
|
340
255
|
|
|
@@ -594,9 +509,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
594
509
|
if absolute_path.name.endswith(".drawio.png") or absolute_path.name.endswith(".drawio.svg"):
|
|
595
510
|
return self._transform_drawio_image(absolute_path, attrs)
|
|
596
511
|
elif absolute_path.name.endswith(".drawio.xml") or absolute_path.name.endswith(".drawio"):
|
|
597
|
-
self.
|
|
598
|
-
image_filename = attachment_name(path_relative_to(absolute_path, self.base_dir))
|
|
599
|
-
return self._create_drawio(image_filename, attrs)
|
|
512
|
+
return self._transform_drawio(absolute_path, attrs)
|
|
600
513
|
else:
|
|
601
514
|
return self._transform_attached_image(absolute_path, attrs)
|
|
602
515
|
|
|
@@ -651,10 +564,28 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
651
564
|
absolute_path = png_file
|
|
652
565
|
|
|
653
566
|
self.images.append(absolute_path)
|
|
654
|
-
|
|
567
|
+
image_name = attachment_name(path_relative_to(absolute_path, self.base_dir))
|
|
568
|
+
return self._create_attached_image(image_name, attrs)
|
|
569
|
+
|
|
570
|
+
def _transform_drawio(self, absolute_path: Path, attrs: ImageAttributes) -> ET._Element:
|
|
571
|
+
"Emits Confluence Storage Format XHTML for a draw.io diagram."
|
|
572
|
+
|
|
573
|
+
if not absolute_path.name.endswith(".drawio.xml") and not absolute_path.name.endswith(".drawio"):
|
|
574
|
+
raise DocumentError("invalid image format; expected: `*.drawio.xml` or `*.drawio`")
|
|
575
|
+
|
|
576
|
+
if self.options.render_drawio:
|
|
577
|
+
image_data = drawio.render_diagram(absolute_path, self.options.diagram_output_format)
|
|
578
|
+
image_hash = hashlib.md5(image_data).hexdigest()
|
|
579
|
+
image_filename = attachment_name(f"embedded_{image_hash}.{self.options.diagram_output_format}")
|
|
580
|
+
self.embedded_images[image_filename] = image_data
|
|
581
|
+
return self._create_attached_image(image_filename, attrs)
|
|
582
|
+
else:
|
|
583
|
+
self.images.append(absolute_path)
|
|
584
|
+
image_filename = attachment_name(path_relative_to(absolute_path, self.base_dir))
|
|
585
|
+
return self._create_drawio(image_filename, attrs)
|
|
655
586
|
|
|
656
587
|
def _transform_drawio_image(self, absolute_path: Path, attrs: ImageAttributes) -> ET._Element:
|
|
657
|
-
"Emits Confluence Storage Format XHTML for a draw.io image."
|
|
588
|
+
"Emits Confluence Storage Format XHTML for a draw.io diagram embedded in a PNG or SVG image."
|
|
658
589
|
|
|
659
590
|
if not absolute_path.name.endswith(".drawio.png") and not absolute_path.name.endswith(".drawio.svg"):
|
|
660
591
|
raise DocumentError("invalid image format; expected: `*.drawio.png` or `*.drawio.svg`")
|
|
@@ -663,17 +594,15 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
663
594
|
return self._transform_attached_image(absolute_path, attrs)
|
|
664
595
|
else:
|
|
665
596
|
# extract embedded editable diagram and upload as *.drawio
|
|
666
|
-
image_data = extract_diagram(absolute_path)
|
|
597
|
+
image_data = drawio.extract_diagram(absolute_path)
|
|
667
598
|
image_filename = attachment_name(path_relative_to(absolute_path.with_suffix(".xml"), self.base_dir))
|
|
668
599
|
self.embedded_images[image_filename] = image_data
|
|
669
600
|
|
|
670
601
|
return self._create_drawio(image_filename, attrs)
|
|
671
602
|
|
|
672
|
-
def
|
|
603
|
+
def _create_attached_image(self, image_name: str, attrs: ImageAttributes) -> ET._Element:
|
|
673
604
|
"An image embedded into the page, linking to an attachment."
|
|
674
605
|
|
|
675
|
-
image_name = attachment_name(path_relative_to(absolute_path, self.base_dir))
|
|
676
|
-
|
|
677
606
|
attributes: dict[str, Any] = {
|
|
678
607
|
ET.QName(namespaces["ac"], "align"): "center",
|
|
679
608
|
ET.QName(namespaces["ac"], "layout"): "center",
|
|
@@ -803,21 +732,11 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
803
732
|
"Transforms a Mermaid diagram code block."
|
|
804
733
|
|
|
805
734
|
if self.options.render_mermaid:
|
|
806
|
-
image_data = render_diagram(content, self.options.diagram_output_format)
|
|
735
|
+
image_data = mermaid.render_diagram(content, self.options.diagram_output_format)
|
|
807
736
|
image_hash = hashlib.md5(image_data).hexdigest()
|
|
808
737
|
image_filename = attachment_name(f"embedded_{image_hash}.{self.options.diagram_output_format}")
|
|
809
738
|
self.embedded_images[image_filename] = image_data
|
|
810
|
-
return
|
|
811
|
-
"image",
|
|
812
|
-
{
|
|
813
|
-
ET.QName(namespaces["ac"], "align"): "center",
|
|
814
|
-
ET.QName(namespaces["ac"], "layout"): "center",
|
|
815
|
-
},
|
|
816
|
-
RI(
|
|
817
|
-
"attachment",
|
|
818
|
-
{ET.QName(namespaces["ri"], "filename"): image_filename},
|
|
819
|
-
),
|
|
820
|
-
)
|
|
739
|
+
return self._create_attached_image(image_filename, ImageAttributes(None, None, None))
|
|
821
740
|
else:
|
|
822
741
|
local_id = str(uuid.uuid4())
|
|
823
742
|
macro_id = str(uuid.uuid4())
|
|
@@ -1388,48 +1307,6 @@ class DocumentError(RuntimeError):
|
|
|
1388
1307
|
"Raised when a converted Markdown document has an unexpected element or attribute."
|
|
1389
1308
|
|
|
1390
1309
|
|
|
1391
|
-
@dataclass
|
|
1392
|
-
class ConfluencePageID:
|
|
1393
|
-
page_id: str
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
@dataclass
|
|
1397
|
-
class ConfluenceQualifiedID:
|
|
1398
|
-
page_id: str
|
|
1399
|
-
space_key: str
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
@dataclass
|
|
1403
|
-
class ConfluenceDocumentOptions:
|
|
1404
|
-
"""
|
|
1405
|
-
Options that control the generated page content.
|
|
1406
|
-
|
|
1407
|
-
:param ignore_invalid_url: When true, ignore invalid URLs in input, emit a warning and replace the anchor with
|
|
1408
|
-
plain text; when false, raise an exception.
|
|
1409
|
-
:param heading_anchors: When true, emit a structured macro *anchor* for each section heading using GitHub
|
|
1410
|
-
conversion rules for the identifier.
|
|
1411
|
-
:param generated_by: Text to use as the generated-by prompt (or `None` to omit a prompt).
|
|
1412
|
-
:param root_page_id: Confluence page to assume root page role for publishing a directory of Markdown files.
|
|
1413
|
-
:param keep_hierarchy: Whether to maintain source directory structure when exporting to Confluence.
|
|
1414
|
-
:param prefer_raster: Whether to choose PNG files over SVG files when available.
|
|
1415
|
-
:param render_drawio: Whether to pre-render (or use the pre-rendered version of) draw.io diagrams.
|
|
1416
|
-
:param render_mermaid: Whether to pre-render Mermaid diagrams into PNG/SVG images.
|
|
1417
|
-
:param diagram_output_format: Target image format for diagrams.
|
|
1418
|
-
:param webui_links: When true, convert relative URLs to Confluence Web UI links.
|
|
1419
|
-
"""
|
|
1420
|
-
|
|
1421
|
-
ignore_invalid_url: bool = False
|
|
1422
|
-
heading_anchors: bool = False
|
|
1423
|
-
generated_by: Optional[str] = "This page has been generated with a tool."
|
|
1424
|
-
root_page_id: Optional[ConfluencePageID] = None
|
|
1425
|
-
keep_hierarchy: bool = False
|
|
1426
|
-
prefer_raster: bool = True
|
|
1427
|
-
render_drawio: bool = False
|
|
1428
|
-
render_mermaid: bool = False
|
|
1429
|
-
diagram_output_format: Literal["png", "svg"] = "png"
|
|
1430
|
-
webui_links: bool = False
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
1310
|
class ConversionError(RuntimeError):
|
|
1434
1311
|
"Raised when a Markdown document cannot be converted to Confluence Storage Format."
|
|
1435
1312
|
|
|
@@ -1507,15 +1384,7 @@ class ConfluenceDocument:
|
|
|
1507
1384
|
raise ConversionError(path) from ex
|
|
1508
1385
|
|
|
1509
1386
|
converter = ConfluenceStorageFormatConverter(
|
|
1510
|
-
ConfluenceConverterOptions(
|
|
1511
|
-
ignore_invalid_url=self.options.ignore_invalid_url,
|
|
1512
|
-
heading_anchors=self.options.heading_anchors,
|
|
1513
|
-
prefer_raster=self.options.prefer_raster,
|
|
1514
|
-
render_drawio=self.options.render_drawio,
|
|
1515
|
-
render_mermaid=self.options.render_mermaid,
|
|
1516
|
-
diagram_output_format=self.options.diagram_output_format,
|
|
1517
|
-
webui_links=self.options.webui_links,
|
|
1518
|
-
),
|
|
1387
|
+
ConfluenceConverterOptions(**{field.name: getattr(self.options, field.name) for field in dataclasses.fields(ConfluenceConverterOptions)}),
|
|
1519
1388
|
path,
|
|
1520
1389
|
root_dir,
|
|
1521
1390
|
site_metadata,
|
md2conf/domain.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Literal, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ConfluencePageID:
|
|
15
|
+
page_id: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ConfluenceDocumentOptions:
|
|
20
|
+
"""
|
|
21
|
+
Options that control the generated page content.
|
|
22
|
+
|
|
23
|
+
:param ignore_invalid_url: When true, ignore invalid URLs in input, emit a warning and replace the anchor with
|
|
24
|
+
plain text; when false, raise an exception.
|
|
25
|
+
:param heading_anchors: When true, emit a structured macro *anchor* for each section heading using GitHub
|
|
26
|
+
conversion rules for the identifier.
|
|
27
|
+
:param generated_by: Text to use as the generated-by prompt (or `None` to omit a prompt).
|
|
28
|
+
:param root_page_id: Confluence page to assume root page role for publishing a directory of Markdown files.
|
|
29
|
+
:param keep_hierarchy: Whether to maintain source directory structure when exporting to Confluence.
|
|
30
|
+
:param prefer_raster: Whether to choose PNG files over SVG files when available.
|
|
31
|
+
:param render_drawio: Whether to pre-render (or use the pre-rendered version of) draw.io diagrams.
|
|
32
|
+
:param render_mermaid: Whether to pre-render Mermaid diagrams into PNG/SVG images.
|
|
33
|
+
:param diagram_output_format: Target image format for diagrams.
|
|
34
|
+
:param webui_links: When true, convert relative URLs to Confluence Web UI links.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
ignore_invalid_url: bool = False
|
|
38
|
+
heading_anchors: bool = False
|
|
39
|
+
generated_by: Optional[str] = "This page has been generated with a tool."
|
|
40
|
+
root_page_id: Optional[ConfluencePageID] = None
|
|
41
|
+
keep_hierarchy: bool = False
|
|
42
|
+
prefer_raster: bool = True
|
|
43
|
+
render_drawio: bool = False
|
|
44
|
+
render_mermaid: bool = False
|
|
45
|
+
diagram_output_format: Literal["png", "svg"] = "png"
|
|
46
|
+
webui_links: bool = False
|
md2conf/drawio.py
CHANGED
|
@@ -7,6 +7,11 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import base64
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import os.path
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
10
15
|
import typing
|
|
11
16
|
import zlib
|
|
12
17
|
from pathlib import Path
|
|
@@ -15,6 +20,8 @@ from urllib.parse import unquote_to_bytes
|
|
|
15
20
|
|
|
16
21
|
import lxml.etree as ET
|
|
17
22
|
|
|
23
|
+
LOGGER = logging.getLogger(__name__)
|
|
24
|
+
|
|
18
25
|
|
|
19
26
|
class DrawioError(ValueError):
|
|
20
27
|
"""
|
|
@@ -220,3 +227,45 @@ def extract_diagram(path: Path) -> bytes:
|
|
|
220
227
|
raise DrawioError(f"unrecognized file type for {path.name}")
|
|
221
228
|
|
|
222
229
|
return ET.tostring(root, encoding="utf8", method="xml")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def render_diagram(source: Path, output_format: typing.Literal["png", "svg"] = "png") -> bytes:
|
|
233
|
+
"Generates a PNG or SVG image from a draw.io diagram source."
|
|
234
|
+
|
|
235
|
+
executable = shutil.which("draw.io")
|
|
236
|
+
if executable is None:
|
|
237
|
+
raise DrawioError("draw.io executable not found")
|
|
238
|
+
|
|
239
|
+
target = f"tmp_drawio.{output_format}"
|
|
240
|
+
|
|
241
|
+
cmd = [executable, "--export", "--format", output_format, "--output", target]
|
|
242
|
+
if output_format == "png":
|
|
243
|
+
cmd.extend(["--scale", "2", "--transparent"])
|
|
244
|
+
elif output_format == "svg":
|
|
245
|
+
cmd.append("--embed-svg-images")
|
|
246
|
+
cmd.append(str(source))
|
|
247
|
+
|
|
248
|
+
LOGGER.debug("Executing: %s", " ".join(cmd))
|
|
249
|
+
try:
|
|
250
|
+
proc = subprocess.Popen(
|
|
251
|
+
cmd,
|
|
252
|
+
stdout=subprocess.PIPE,
|
|
253
|
+
stderr=subprocess.PIPE,
|
|
254
|
+
text=False,
|
|
255
|
+
)
|
|
256
|
+
stdout, stderr = proc.communicate()
|
|
257
|
+
if proc.returncode:
|
|
258
|
+
messages = [f"failed to convert draw.io diagram; exit code: {proc.returncode}"]
|
|
259
|
+
console_output = stdout.decode("utf-8")
|
|
260
|
+
if console_output:
|
|
261
|
+
messages.append(f"output:\n{console_output}")
|
|
262
|
+
console_error = stderr.decode("utf-8")
|
|
263
|
+
if console_error:
|
|
264
|
+
messages.append(f"error:\n{console_error}")
|
|
265
|
+
raise DrawioError("\n".join(messages))
|
|
266
|
+
with open(target, "rb") as f:
|
|
267
|
+
return f.read()
|
|
268
|
+
|
|
269
|
+
finally:
|
|
270
|
+
if os.path.exists(target):
|
|
271
|
+
os.remove(target)
|
md2conf/local.py
CHANGED
|
@@ -11,7 +11,8 @@ import os
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Optional
|
|
13
13
|
|
|
14
|
-
from .converter import ConfluenceDocument
|
|
14
|
+
from .converter import ConfluenceDocument
|
|
15
|
+
from .domain import ConfluenceDocumentOptions, ConfluencePageID
|
|
15
16
|
from .extra import override
|
|
16
17
|
from .metadata import ConfluencePageMetadata, ConfluenceSiteMetadata
|
|
17
18
|
from .processor import Converter, DocumentNode, Processor, ProcessorFactory
|
|
@@ -77,10 +78,14 @@ class LocalProcessor(Processor):
|
|
|
77
78
|
"""
|
|
78
79
|
|
|
79
80
|
content = document.xhtml()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
csf_path = self.out_dir / path.relative_to(self.root_dir).with_suffix(".csf")
|
|
82
|
+
csf_dir = csf_path.parent
|
|
83
|
+
os.makedirs(csf_dir, exist_ok=True)
|
|
84
|
+
with open(csf_path, "w", encoding="utf-8") as f:
|
|
83
85
|
f.write(content)
|
|
86
|
+
for name, data in document.embedded_images.items():
|
|
87
|
+
with open(csf_dir / name, "wb") as f:
|
|
88
|
+
f.write(data)
|
|
84
89
|
|
|
85
90
|
|
|
86
91
|
class LocalProcessorFactory(ProcessorFactory):
|
md2conf/markdown.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import xml.etree.ElementTree
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
import markdown
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _emoji_generator(
|
|
16
|
+
index: str,
|
|
17
|
+
shortname: str,
|
|
18
|
+
alias: Optional[str],
|
|
19
|
+
uc: Optional[str],
|
|
20
|
+
alt: str,
|
|
21
|
+
title: Optional[str],
|
|
22
|
+
category: Optional[str],
|
|
23
|
+
options: dict[str, Any],
|
|
24
|
+
md: markdown.Markdown,
|
|
25
|
+
) -> xml.etree.ElementTree.Element:
|
|
26
|
+
"""
|
|
27
|
+
Custom generator for `pymdownx.emoji`.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name = (alias or shortname).strip(":")
|
|
31
|
+
span = xml.etree.ElementTree.Element("span", {"data-emoji-shortname": name})
|
|
32
|
+
if uc is not None:
|
|
33
|
+
span.attrib["data-emoji-unicode"] = uc
|
|
34
|
+
|
|
35
|
+
# convert series of Unicode code point hexadecimal values into characters
|
|
36
|
+
span.text = "".join(chr(int(item, base=16)) for item in uc.split("-"))
|
|
37
|
+
else:
|
|
38
|
+
span.text = alt
|
|
39
|
+
return span
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _math_formatter(
|
|
43
|
+
source: str,
|
|
44
|
+
language: str,
|
|
45
|
+
css_class: str,
|
|
46
|
+
options: dict[str, Any],
|
|
47
|
+
md: markdown.Markdown,
|
|
48
|
+
classes: Optional[list[str]] = None,
|
|
49
|
+
id_value: str = "",
|
|
50
|
+
attrs: Optional[dict[str, str]] = None,
|
|
51
|
+
**kwargs: Any,
|
|
52
|
+
) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Custom formatter for language `math` in `pymdownx.superfences`.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
if classes is None:
|
|
58
|
+
classes = [css_class]
|
|
59
|
+
else:
|
|
60
|
+
classes.insert(0, css_class)
|
|
61
|
+
|
|
62
|
+
html_id = f' id="{id_value}"' if id_value else ""
|
|
63
|
+
html_class = ' class="{}"'.format(" ".join(classes))
|
|
64
|
+
html_attrs = " " + " ".join(f'{k}="{v}"' for k, v in attrs.items()) if attrs else ""
|
|
65
|
+
|
|
66
|
+
return f"<div{html_id}{html_class}{html_attrs}>{source}</div>"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_CONVERTER = markdown.Markdown(
|
|
70
|
+
extensions=[
|
|
71
|
+
"admonition",
|
|
72
|
+
"footnotes",
|
|
73
|
+
"markdown.extensions.tables",
|
|
74
|
+
"md_in_html",
|
|
75
|
+
"pymdownx.arithmatex",
|
|
76
|
+
"pymdownx.emoji",
|
|
77
|
+
"pymdownx.highlight", # required by `pymdownx.superfences`
|
|
78
|
+
"pymdownx.magiclink",
|
|
79
|
+
"pymdownx.superfences",
|
|
80
|
+
"pymdownx.tilde",
|
|
81
|
+
"sane_lists",
|
|
82
|
+
],
|
|
83
|
+
extension_configs={
|
|
84
|
+
"footnotes": {"BACKLINK_TITLE": ""},
|
|
85
|
+
"pymdownx.arithmatex": {"generic": True, "preview": False, "tex_inline_wrap": ["", ""], "tex_block_wrap": ["", ""]},
|
|
86
|
+
"pymdownx.emoji": {
|
|
87
|
+
"emoji_generator": _emoji_generator,
|
|
88
|
+
},
|
|
89
|
+
"pymdownx.highlight": {
|
|
90
|
+
"use_pygments": False,
|
|
91
|
+
},
|
|
92
|
+
"pymdownx.superfences": {"custom_fences": [{"name": "math", "class": "arithmatex", "format": _math_formatter}]},
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def markdown_to_html(content: str) -> str:
|
|
98
|
+
"""
|
|
99
|
+
Converts a Markdown document into XHTML with Python-Markdown.
|
|
100
|
+
|
|
101
|
+
:param content: Markdown input as a string.
|
|
102
|
+
:returns: XHTML output as a string.
|
|
103
|
+
:see: https://python-markdown.github.io/
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
_CONVERTER.reset()
|
|
107
|
+
html = _CONVERTER.convert(content)
|
|
108
|
+
return html
|
md2conf/processor.py
CHANGED
|
@@ -14,7 +14,8 @@ from pathlib import Path
|
|
|
14
14
|
from typing import Iterable, Optional
|
|
15
15
|
|
|
16
16
|
from .collection import ConfluencePageCollection
|
|
17
|
-
from .converter import ConfluenceDocument
|
|
17
|
+
from .converter import ConfluenceDocument
|
|
18
|
+
from .domain import ConfluenceDocumentOptions, ConfluencePageID
|
|
18
19
|
from .matcher import DirectoryEntry, FileEntry, Matcher, MatcherOptions
|
|
19
20
|
from .metadata import ConfluenceSiteMetadata
|
|
20
21
|
from .properties import ArgumentError
|
|
File without changes
|
{markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{markdown_to_confluence-0.4.2.dist-info → markdown_to_confluence-0.4.3.dist-info}/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|