markdown-to-confluence 0.5.3__py3-none-any.whl → 0.5.5__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.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/METADATA +275 -208
- markdown_to_confluence-0.5.5.dist-info/RECORD +57 -0
- {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/WHEEL +1 -1
- md2conf/__init__.py +1 -1
- md2conf/__main__.py +61 -189
- md2conf/api.py +35 -69
- md2conf/attachment.py +4 -3
- md2conf/clio.py +226 -0
- md2conf/compatibility.py +5 -0
- md2conf/converter.py +239 -147
- md2conf/csf.py +89 -9
- md2conf/drawio/extension.py +3 -3
- md2conf/drawio/render.py +2 -0
- md2conf/extension.py +4 -0
- md2conf/external.py +25 -8
- md2conf/frontmatter.py +18 -6
- md2conf/image.py +17 -14
- md2conf/latex.py +8 -1
- md2conf/markdown.py +68 -1
- md2conf/mermaid/render.py +1 -1
- md2conf/options.py +95 -24
- md2conf/plantuml/extension.py +7 -7
- md2conf/plantuml/render.py +6 -7
- md2conf/png.py +10 -6
- md2conf/processor.py +24 -3
- md2conf/publisher.py +193 -36
- md2conf/reflection.py +74 -0
- md2conf/scanner.py +16 -6
- md2conf/serializer.py +12 -1
- md2conf/svg.py +131 -109
- md2conf/toc.py +72 -0
- md2conf/xml.py +45 -0
- markdown_to_confluence-0.5.3.dist-info/RECORD +0 -55
- {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/licenses/LICENSE +0 -0
- {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/top_level.txt +0 -0
- {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/zip-safe +0 -0
- /md2conf/{puppeteer-config.json → mermaid/puppeteer-config.json} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
markdown_to_confluence-0.5.5.dist-info/licenses/LICENSE,sha256=SEEBf2BMI1LUHnDvyHnV6L12A6zTAOQcsyMvaawAXWo,1077
|
|
2
|
+
md2conf/__init__.py,sha256=TlQmZzCRIxd5nfwKqbjScEoeeE6okogDHEHRyXzmQBo,402
|
|
3
|
+
md2conf/__main__.py,sha256=4D4OiRJUY1m0UnhndIjg9bOFfAOsuyw12OzgxzypFnw,9480
|
|
4
|
+
md2conf/api.py,sha256=6lJnu-daNxDhOoJdTQ3GAyt6RkFSIP92iC4ptm8sNhs,43028
|
|
5
|
+
md2conf/attachment.py,sha256=3fGLXX3utOP9dZeZqfs9sFiwH5yFgk7ixfhKbTfUb8U,1720
|
|
6
|
+
md2conf/clio.py,sha256=pyiZKipOTQUqR6f0fblchPL1yAr6mM_GPCzS_-Kv1aQ,7851
|
|
7
|
+
md2conf/coalesce.py,sha256=YHnqFwow5wCj6OQ3oosig01D2lxWusAScMF4HAUO2-g,1305
|
|
8
|
+
md2conf/collection.py,sha256=ukN74VCa4HaGSh6tLXpLd0j_UNPywcnKI0X7usgdSCo,824
|
|
9
|
+
md2conf/compatibility.py,sha256=b6n_JFRlNU5Jx4RJqS7FgMp4tk0iYGFxb4--fkIsQcM,873
|
|
10
|
+
md2conf/converter.py,sha256=M7jILSSlY_G1WsyQ9tcZ3MsNmGH1T1GTUIiJQ0SSjlY,66627
|
|
11
|
+
md2conf/csf.py,sha256=w-ng621pWbECwlAwVOWrH0mszG_v9Ac0W39RtHyNa2w,8365
|
|
12
|
+
md2conf/emoticon.py,sha256=0g4rkx3d58xU4nnLak5ms7i0FSDnq0WJrLVFRgGyLC8,542
|
|
13
|
+
md2conf/entities.dtd,sha256=M6NzqL5N7dPs_eUA_6sDsiSLzDaAacrx9LdttiufvYU,30215
|
|
14
|
+
md2conf/environment.py,sha256=TfNEz3Pyw9qe7f8i7e_kph16c09fhZ4cLNZZzIjmI18,3892
|
|
15
|
+
md2conf/extension.py,sha256=_IBf_yhYb6luQM3A-vAAtCpjHay33kE4Au_SGuC3kow,2274
|
|
16
|
+
md2conf/external.py,sha256=uY1G7bdqEMJW66vOvKsh5CS4oHY-YA7h2VVuaSdaqBo,2366
|
|
17
|
+
md2conf/formatting.py,sha256=ygL59VgpioX069axEX-7XjKs0sUjTfIZiBE5fWmITxc,4557
|
|
18
|
+
md2conf/frontmatter.py,sha256=3hUvrxvopI90UxJX7BLwLkjsC6LKLVr4mJ_ctB4r-po,2263
|
|
19
|
+
md2conf/image.py,sha256=E9sLj9xU41pKHBo_8saBcyOuJMwNPwVQFIMmW_7QTzA,5257
|
|
20
|
+
md2conf/latex.py,sha256=RukmO19gvmOFIfWTyBNth1r7FlnihvX56R1EWiaNRdU,2466
|
|
21
|
+
md2conf/local.py,sha256=eY3WpY-lNzLZeAfxX1ACVEhuzz0HDYX_sNQogJfkqcM,3673
|
|
22
|
+
md2conf/markdown.py,sha256=Okhzod811q5-AGn1dOdsYKeB8jBE0GXD2d4_k7Dk9y8,5939
|
|
23
|
+
md2conf/matcher.py,sha256=Xg4YSb87iPkCzhKuKytBut6NOkEab3IM-AjzXbwy64U,6774
|
|
24
|
+
md2conf/metadata.py,sha256=NOjbCIrwLgTIIeNgmo7w5JXuT-pxOXBGSg-irfdpokk,976
|
|
25
|
+
md2conf/options.py,sha256=G6J2l7JW__bIjpTJEQfiFkX3mdB9Q1XbJoN9xduzt3k,7993
|
|
26
|
+
md2conf/png.py,sha256=GU3-0dG6HqwGjedJVUciaIdA-6CdPTy_clsOQGr6dGE,6251
|
|
27
|
+
md2conf/processor.py,sha256=xVLpvKg2FEO0tWsHQ8sm7YpimQepbZ07W0_yUzcvl6c,11116
|
|
28
|
+
md2conf/publisher.py,sha256=n0YkMPw0YZQAUyj-PEW5RSes8YNxIEexFMSMtA7jrTY,14435
|
|
29
|
+
md2conf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
md2conf/reflection.py,sha256=9b2X9BEisVHpZY5SWv89Z5ZucZ7bO1dIKmWGiooMS68,2638
|
|
31
|
+
md2conf/scanner.py,sha256=2OYBUxooIm8dJBzYMxB3YjdWam6QiNA6fFi5BocvFTo,4217
|
|
32
|
+
md2conf/serializer.py,sha256=fkSzSIPUMSOze4NrpYsqzfFrXQiRYROO9HrueQZoeSo,2144
|
|
33
|
+
md2conf/svg.py,sha256=dR4dnwbjoQq9iIM4nH9PQ4SUdTrZS0BKt0hMyO4C8qE,11607
|
|
34
|
+
md2conf/text.py,sha256=cnYV_JQp_v91LbQHo3qvxcEuhIdaPjCjkmLOKINcNv4,1736
|
|
35
|
+
md2conf/toc.py,sha256=g1mmfcK3c4qff-oQM9dr6s_6gF7WTMbNXoxsDAsiJBY,4614
|
|
36
|
+
md2conf/uri.py,sha256=my0deyR5SlppJrYCbXF1Zz94QA1JT-HTWe9pKw7AJ_A,1158
|
|
37
|
+
md2conf/xml.py,sha256=eR41FaqiketsCxTFBhwKEUW4g1ZQohvlh5J5WC3X-7Q,6690
|
|
38
|
+
md2conf/drawio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
+
md2conf/drawio/extension.py,sha256=HHLUriTfg82VCfOEyzU-6j2IM9rxR3I1UdSDdujWHgU,4409
|
|
40
|
+
md2conf/drawio/render.py,sha256=BgQRIScH_JpiVX7YTY543x7hQjThskqHf_w9YQ9Y5o0,8636
|
|
41
|
+
md2conf/mermaid/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
md2conf/mermaid/config.py,sha256=5Dec2QcdB_GtnuXIW6nhJK8J5caduNZU1oz1mcmmb44,376
|
|
43
|
+
md2conf/mermaid/extension.py,sha256=1drXVM_KbS00dcjSCRru0wwbil4zq3aR81dHMhfe7zA,4021
|
|
44
|
+
md2conf/mermaid/puppeteer-config.json,sha256=-dMTAN_7kNTGbDlfXzApl0KJpAWna9YKZdwMKbpOb60,159
|
|
45
|
+
md2conf/mermaid/render.py,sha256=zO6M5UWSKiezoxPojD8iwFnwrFEDw_P6liQi-C3LQgw,1817
|
|
46
|
+
md2conf/mermaid/scanner.py,sha256=oIpaNxiZBNcmggnjlyYGcIVOXcYQWjf1lEVdyIwE4xE,1379
|
|
47
|
+
md2conf/plantuml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
md2conf/plantuml/config.py,sha256=j0ONhkzmAPagh00ltamTKlVEvXa6R284We9pDxRy-5U,378
|
|
49
|
+
md2conf/plantuml/extension.py,sha256=TPwcQHw9s76GThVQVdgcPB0THmnDFPly2Zqei6pG63I,6285
|
|
50
|
+
md2conf/plantuml/render.py,sha256=Lf1It2KxHPKNGM1rhIDg9zdC3iqhRNCduByqa0_k_qw,3725
|
|
51
|
+
md2conf/plantuml/scanner.py,sha256=Oso6VbHVuMaPMKMazQc_bf4hhOT5WeJN5WiVPM8peyM,1347
|
|
52
|
+
markdown_to_confluence-0.5.5.dist-info/METADATA,sha256=zpy0fBKhzk-S2JcDLMZefDDTI5jrF1hTfIQkY7zyKg0,47514
|
|
53
|
+
markdown_to_confluence-0.5.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
54
|
+
markdown_to_confluence-0.5.5.dist-info/entry_points.txt,sha256=F1zxa1wtEObtbHS-qp46330WVFLHdMnV2wQ-ZorRmX0,50
|
|
55
|
+
markdown_to_confluence-0.5.5.dist-info/top_level.txt,sha256=_FJfl_kHrHNidyjUOuS01ngu_jDsfc-ZjSocNRJnTzU,8
|
|
56
|
+
markdown_to_confluence-0.5.5.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
57
|
+
markdown_to_confluence-0.5.5.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.5.
|
|
8
|
+
__version__ = "0.5.5"
|
|
9
9
|
__author__ = "Levente Hunyadi"
|
|
10
10
|
__copyright__ = "Copyright 2022-2026, Levente Hunyadi"
|
|
11
11
|
__license__ = "MIT"
|
md2conf/__main__.py
CHANGED
|
@@ -16,17 +16,23 @@ import sys
|
|
|
16
16
|
import typing
|
|
17
17
|
from io import StringIO
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from
|
|
19
|
+
from types import TracebackType
|
|
20
|
+
from typing import Any, Iterable, Sequence
|
|
21
|
+
|
|
22
|
+
from requests.exceptions import HTTPError, JSONDecodeError
|
|
20
23
|
|
|
21
24
|
from . import __version__
|
|
25
|
+
from .clio import add_arguments, get_options
|
|
22
26
|
from .compatibility import override
|
|
23
27
|
from .environment import ArgumentError, ConfluenceSiteProperties, ConnectionProperties
|
|
24
28
|
from .metadata import ConfluenceSiteMetadata
|
|
25
|
-
from .options import ConfluencePageID, ConverterOptions, DocumentOptions
|
|
29
|
+
from .options import ConfluencePageID, ConverterOptions, DocumentOptions
|
|
30
|
+
|
|
31
|
+
LOGGER = logging.getLogger(__name__)
|
|
26
32
|
|
|
27
33
|
|
|
28
34
|
class Arguments(argparse.Namespace):
|
|
29
|
-
mdpath: Path
|
|
35
|
+
mdpath: list[Path]
|
|
30
36
|
domain: str | None
|
|
31
37
|
path: str | None
|
|
32
38
|
api_url: str | None
|
|
@@ -34,25 +40,14 @@ class Arguments(argparse.Namespace):
|
|
|
34
40
|
api_key: str | None
|
|
35
41
|
space: str | None
|
|
36
42
|
loglevel: str
|
|
37
|
-
heading_anchors: bool
|
|
38
|
-
ignore_invalid_url: bool
|
|
39
43
|
root_page: str | None
|
|
40
44
|
keep_hierarchy: bool
|
|
41
|
-
skip_title_heading: bool
|
|
42
45
|
title_prefix: str | None
|
|
43
46
|
generated_by: str | None
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
render_mermaid: bool
|
|
47
|
-
render_plantuml: bool
|
|
48
|
-
render_latex: bool
|
|
49
|
-
diagram_output_format: Literal["png", "svg"]
|
|
47
|
+
skip_update: bool
|
|
48
|
+
line_numbers: bool
|
|
50
49
|
local: bool
|
|
51
50
|
headers: dict[str, str]
|
|
52
|
-
webui_links: bool
|
|
53
|
-
alignment: Literal["center", "left", "right"]
|
|
54
|
-
max_image_width: int | None
|
|
55
|
-
use_panel: bool
|
|
56
51
|
|
|
57
52
|
|
|
58
53
|
class KwargsAppendAction(argparse.Action):
|
|
@@ -100,7 +95,7 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
100
95
|
parser = argparse.ArgumentParser(formatter_class=PositionalOnlyHelpFormatter)
|
|
101
96
|
parser.prog = os.path.basename(os.path.dirname(__file__))
|
|
102
97
|
parser.add_argument("--version", action="version", version=__version__)
|
|
103
|
-
parser.add_argument("mdpath", help="Path to Markdown file or directory to convert and publish.")
|
|
98
|
+
parser.add_argument("mdpath", type=Path, nargs="+", help="Path to Markdown file or directory to convert and publish.")
|
|
104
99
|
parser.add_argument("-d", "--domain", help="Confluence organization domain.")
|
|
105
100
|
parser.add_argument("-p", "--path", help="Base path for Confluence (default: '/wiki/').")
|
|
106
101
|
parser.add_argument(
|
|
@@ -123,16 +118,7 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
123
118
|
parser.add_argument(
|
|
124
119
|
"-l",
|
|
125
120
|
"--loglevel",
|
|
126
|
-
choices=[
|
|
127
|
-
logging.getLevelName(level).lower()
|
|
128
|
-
for level in (
|
|
129
|
-
logging.DEBUG,
|
|
130
|
-
logging.INFO,
|
|
131
|
-
logging.WARN,
|
|
132
|
-
logging.ERROR,
|
|
133
|
-
logging.CRITICAL,
|
|
134
|
-
)
|
|
135
|
-
],
|
|
121
|
+
choices=[logging.getLevelName(level).lower() for level in (logging.DEBUG, logging.INFO, logging.WARN, logging.ERROR, logging.CRITICAL)],
|
|
136
122
|
default=logging.getLevelName(logging.INFO),
|
|
137
123
|
help="Use this option to set the log verbosity.",
|
|
138
124
|
)
|
|
@@ -148,16 +134,16 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
148
134
|
help="Maintain source directory structure when exporting to Confluence.",
|
|
149
135
|
)
|
|
150
136
|
parser.add_argument(
|
|
151
|
-
"--
|
|
137
|
+
"--skip-hierarchy",
|
|
152
138
|
dest="keep_hierarchy",
|
|
153
139
|
action="store_false",
|
|
154
|
-
help="Flatten directories with no index.md or README.md when exporting to Confluence.",
|
|
140
|
+
help="Flatten directories with no `index.md` or `README.md` when exporting to Confluence.",
|
|
155
141
|
)
|
|
156
142
|
parser.add_argument(
|
|
157
143
|
"--generated-by",
|
|
158
144
|
default="This page has been generated with a tool.",
|
|
159
145
|
metavar="MARKDOWN",
|
|
160
|
-
help="Add prompt to pages
|
|
146
|
+
help="Add prompt to pages.",
|
|
161
147
|
)
|
|
162
148
|
parser.add_argument(
|
|
163
149
|
"--no-generated-by",
|
|
@@ -167,107 +153,20 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
167
153
|
help="Do not add 'generated by a tool' prompt to pages.",
|
|
168
154
|
)
|
|
169
155
|
parser.add_argument(
|
|
170
|
-
"--
|
|
171
|
-
dest="render_drawio",
|
|
172
|
-
action="store_true",
|
|
173
|
-
default=True,
|
|
174
|
-
help="Render draw.io diagrams as image files. (Installed utility required to covert.)",
|
|
175
|
-
)
|
|
176
|
-
parser.add_argument(
|
|
177
|
-
"--no-render-drawio",
|
|
178
|
-
dest="render_drawio",
|
|
179
|
-
action="store_false",
|
|
180
|
-
help="Upload draw.io diagram sources as Confluence page attachments. (Marketplace app required to display.)",
|
|
181
|
-
)
|
|
182
|
-
parser.add_argument(
|
|
183
|
-
"--render-mermaid",
|
|
184
|
-
dest="render_mermaid",
|
|
185
|
-
action="store_true",
|
|
186
|
-
default=True,
|
|
187
|
-
help="Render Mermaid diagrams as image files. (Installed utility required to convert.)",
|
|
188
|
-
)
|
|
189
|
-
parser.add_argument(
|
|
190
|
-
"--no-render-mermaid",
|
|
191
|
-
dest="render_mermaid",
|
|
192
|
-
action="store_false",
|
|
193
|
-
help="Upload Mermaid diagram sources as Confluence page attachments. (Marketplace app required to display.)",
|
|
194
|
-
)
|
|
195
|
-
parser.add_argument(
|
|
196
|
-
"--render-plantuml",
|
|
197
|
-
dest="render_plantuml",
|
|
198
|
-
action="store_true",
|
|
199
|
-
default=True,
|
|
200
|
-
help="Render PlantUML diagrams as image files. (Installed utility required to convert.)",
|
|
201
|
-
)
|
|
202
|
-
parser.add_argument(
|
|
203
|
-
"--no-render-plantuml",
|
|
204
|
-
dest="render_plantuml",
|
|
205
|
-
action="store_false",
|
|
206
|
-
help="Upload PlantUML diagram sources as Confluence page attachments. (Marketplace app required to display.)",
|
|
207
|
-
)
|
|
208
|
-
parser.add_argument(
|
|
209
|
-
"--render-latex",
|
|
210
|
-
dest="render_latex",
|
|
211
|
-
action="store_true",
|
|
212
|
-
default=True,
|
|
213
|
-
help="Render LaTeX formulas as image files. (Matplotlib required to convert.)",
|
|
214
|
-
)
|
|
215
|
-
parser.add_argument(
|
|
216
|
-
"--no-render-latex",
|
|
217
|
-
dest="render_latex",
|
|
218
|
-
action="store_false",
|
|
219
|
-
help="Inline LaTeX formulas in Confluence page. (Marketplace app required to display.)",
|
|
220
|
-
)
|
|
221
|
-
parser.add_argument(
|
|
222
|
-
"--diagram-output-format",
|
|
223
|
-
dest="diagram_output_format",
|
|
224
|
-
choices=["png", "svg"],
|
|
225
|
-
default="png",
|
|
226
|
-
help="Format for rendering Mermaid and draw.io diagrams (default: 'png').",
|
|
227
|
-
)
|
|
228
|
-
parser.add_argument(
|
|
229
|
-
"--prefer-raster",
|
|
230
|
-
dest="prefer_raster",
|
|
231
|
-
action="store_true",
|
|
232
|
-
default=True,
|
|
233
|
-
help="Prefer PNG over SVG when both exist (default: enabled).",
|
|
234
|
-
)
|
|
235
|
-
parser.add_argument(
|
|
236
|
-
"--no-prefer-raster",
|
|
237
|
-
dest="prefer_raster",
|
|
238
|
-
action="store_false",
|
|
239
|
-
help="Use SVG files directly instead of preferring PNG equivalents.",
|
|
240
|
-
)
|
|
241
|
-
parser.add_argument(
|
|
242
|
-
"--heading-anchors",
|
|
156
|
+
"--skip-update",
|
|
243
157
|
action="store_true",
|
|
244
158
|
default=False,
|
|
245
|
-
help="
|
|
246
|
-
)
|
|
247
|
-
parser
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
default=False,
|
|
257
|
-
help="Emit a warning but otherwise ignore relative URLs that point to ill-specified locations.",
|
|
258
|
-
)
|
|
259
|
-
parser.add_argument(
|
|
260
|
-
"--skip-title-heading",
|
|
261
|
-
action="store_true",
|
|
262
|
-
default=False,
|
|
263
|
-
help="Skip the first heading from document body when it is used as the page title (does not apply if title comes from front-matter).",
|
|
264
|
-
)
|
|
265
|
-
parser.add_argument(
|
|
266
|
-
"--no-skip-title-heading",
|
|
267
|
-
dest="skip_title_heading",
|
|
268
|
-
action="store_false",
|
|
269
|
-
help="Keep the first heading in document body even when used as page title (default).",
|
|
270
|
-
)
|
|
159
|
+
help="Skip saving Confluence page ID in Markdown files.",
|
|
160
|
+
)
|
|
161
|
+
add_arguments(parser, ConverterOptions)
|
|
162
|
+
if sys.version_info >= (3, 13):
|
|
163
|
+
parser.add_argument(
|
|
164
|
+
"--ignore-invalid-url",
|
|
165
|
+
dest="force_valid_url",
|
|
166
|
+
action="store_false",
|
|
167
|
+
help="Emit a warning but otherwise ignore relative URLs that point to ill-specified locations.",
|
|
168
|
+
deprecated=True,
|
|
169
|
+
)
|
|
271
170
|
parser.add_argument(
|
|
272
171
|
"--title-prefix",
|
|
273
172
|
default=None,
|
|
@@ -275,30 +174,11 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
275
174
|
help="String to prepend to Confluence page title for each published page.",
|
|
276
175
|
)
|
|
277
176
|
parser.add_argument(
|
|
278
|
-
"--
|
|
279
|
-
|
|
280
|
-
default=False,
|
|
281
|
-
help="Enable Confluence Web UI links. (Typically required for on-prem versions of Confluence.)",
|
|
282
|
-
)
|
|
283
|
-
parser.add_argument(
|
|
284
|
-
"--alignment",
|
|
285
|
-
dest="alignment",
|
|
286
|
-
choices=["center", "left", "right"],
|
|
287
|
-
default="center",
|
|
288
|
-
help="Alignment for block-level images and formulas (default: 'center').",
|
|
289
|
-
)
|
|
290
|
-
parser.add_argument(
|
|
291
|
-
"--max-image-width",
|
|
292
|
-
dest="max_image_width",
|
|
293
|
-
type=int,
|
|
294
|
-
default=None,
|
|
295
|
-
help="Maximum display width for images [px]. Wider images are scaled down for page display. Original size kept for full-size viewing.",
|
|
296
|
-
)
|
|
297
|
-
parser.add_argument(
|
|
298
|
-
"--use-panel",
|
|
177
|
+
"--line-numbers",
|
|
178
|
+
dest="line_numbers",
|
|
299
179
|
action="store_true",
|
|
300
180
|
default=False,
|
|
301
|
-
help="
|
|
181
|
+
help="Inject line numbers in Markdown source to help localize conversion errors.",
|
|
302
182
|
)
|
|
303
183
|
parser.add_argument(
|
|
304
184
|
"--local",
|
|
@@ -324,13 +204,31 @@ def get_help() -> str:
|
|
|
324
204
|
return buf.getvalue()
|
|
325
205
|
|
|
326
206
|
|
|
207
|
+
def _exception_hook(exc_type: type[BaseException], exc_value: BaseException, traceback: TracebackType | None) -> None:
|
|
208
|
+
LOGGER.exception("Exception raised: %s", exc_type.__name__, exc_info=exc_value)
|
|
209
|
+
ex: BaseException | None = exc_value
|
|
210
|
+
while ex is not None:
|
|
211
|
+
print(f"\033[95m{ex.__class__.__name__}\033[0m: {ex}")
|
|
212
|
+
|
|
213
|
+
if isinstance(ex, HTTPError):
|
|
214
|
+
# print details for a response with JSON body
|
|
215
|
+
if ex.response is not None:
|
|
216
|
+
try:
|
|
217
|
+
LOGGER.error(ex.response.json())
|
|
218
|
+
except JSONDecodeError:
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
ex = ex.__cause__
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
sys.excepthook = _exception_hook # spellchecker:disable-line
|
|
225
|
+
|
|
226
|
+
|
|
327
227
|
def main() -> None:
|
|
328
228
|
parser = get_parser()
|
|
329
229
|
args = Arguments()
|
|
330
230
|
parser.parse_args(namespace=args)
|
|
331
231
|
|
|
332
|
-
args.mdpath = Path(args.mdpath)
|
|
333
|
-
|
|
334
232
|
logging.basicConfig(
|
|
335
233
|
level=getattr(logging, args.loglevel.upper(), logging.INFO),
|
|
336
234
|
format="%(asctime)s - %(levelname)s - %(funcName)s [%(lineno)d] - %(message)s",
|
|
@@ -341,25 +239,9 @@ def main() -> None:
|
|
|
341
239
|
keep_hierarchy=args.keep_hierarchy,
|
|
342
240
|
title_prefix=args.title_prefix,
|
|
343
241
|
generated_by=args.generated_by,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
skip_title_heading=args.skip_title_heading,
|
|
348
|
-
prefer_raster=args.prefer_raster,
|
|
349
|
-
render_drawio=args.render_drawio,
|
|
350
|
-
render_mermaid=args.render_mermaid,
|
|
351
|
-
render_plantuml=args.render_plantuml,
|
|
352
|
-
render_latex=args.render_latex,
|
|
353
|
-
diagram_output_format=args.diagram_output_format,
|
|
354
|
-
webui_links=args.webui_links,
|
|
355
|
-
use_panel=args.use_panel,
|
|
356
|
-
layout=LayoutOptions(
|
|
357
|
-
image=ImageLayoutOptions(
|
|
358
|
-
alignment=args.alignment,
|
|
359
|
-
max_width=args.max_image_width,
|
|
360
|
-
),
|
|
361
|
-
),
|
|
362
|
-
),
|
|
242
|
+
skip_update=args.skip_update,
|
|
243
|
+
converter=get_options(args, ConverterOptions),
|
|
244
|
+
line_numbers=args.line_numbers,
|
|
363
245
|
)
|
|
364
246
|
if args.local:
|
|
365
247
|
from .local import LocalConverter
|
|
@@ -377,10 +259,10 @@ def main() -> None:
|
|
|
377
259
|
base_path=site_properties.base_path,
|
|
378
260
|
space_key=site_properties.space_key,
|
|
379
261
|
)
|
|
380
|
-
LocalConverter(options, site_metadata)
|
|
262
|
+
converter = LocalConverter(options, site_metadata)
|
|
263
|
+
for item in args.mdpath:
|
|
264
|
+
converter.process(item)
|
|
381
265
|
else:
|
|
382
|
-
from requests import HTTPError, JSONDecodeError
|
|
383
|
-
|
|
384
266
|
from .api import ConfluenceAPI
|
|
385
267
|
from .publisher import Publisher
|
|
386
268
|
|
|
@@ -396,20 +278,10 @@ def main() -> None:
|
|
|
396
278
|
)
|
|
397
279
|
except ArgumentError as e:
|
|
398
280
|
parser.error(str(e))
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
logging.error(err)
|
|
404
|
-
|
|
405
|
-
# print details for a response with JSON body
|
|
406
|
-
if err.response is not None:
|
|
407
|
-
try:
|
|
408
|
-
logging.error(err.response.json())
|
|
409
|
-
except JSONDecodeError:
|
|
410
|
-
pass
|
|
411
|
-
|
|
412
|
-
sys.exit(1)
|
|
281
|
+
with ConfluenceAPI(properties) as api:
|
|
282
|
+
publisher = Publisher(api, options)
|
|
283
|
+
for item in args.mdpath:
|
|
284
|
+
publisher.process(item)
|
|
413
285
|
|
|
414
286
|
|
|
415
287
|
if __name__ == "__main__":
|
md2conf/api.py
CHANGED
|
@@ -402,12 +402,7 @@ class ConfluenceAPI:
|
|
|
402
402
|
)
|
|
403
403
|
return self.session
|
|
404
404
|
|
|
405
|
-
def __exit__(
|
|
406
|
-
self,
|
|
407
|
-
exc_type: type[BaseException] | None,
|
|
408
|
-
exc_val: BaseException | None,
|
|
409
|
-
exc_tb: TracebackType | None,
|
|
410
|
-
) -> None:
|
|
405
|
+
def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None) -> None:
|
|
411
406
|
"""
|
|
412
407
|
Closes an open connection.
|
|
413
408
|
"""
|
|
@@ -429,15 +424,7 @@ class ConfluenceSession:
|
|
|
429
424
|
_space_id_to_key: dict[str, str]
|
|
430
425
|
_space_key_to_id: dict[str, str]
|
|
431
426
|
|
|
432
|
-
def __init__(
|
|
433
|
-
self,
|
|
434
|
-
session: requests.Session,
|
|
435
|
-
*,
|
|
436
|
-
api_url: str | None,
|
|
437
|
-
domain: str | None,
|
|
438
|
-
base_path: str | None,
|
|
439
|
-
space_key: str | None,
|
|
440
|
-
) -> None:
|
|
427
|
+
def __init__(self, session: requests.Session, *, api_url: str | None, domain: str | None, base_path: str | None, space_key: str | None) -> None:
|
|
441
428
|
self.session = session
|
|
442
429
|
self._space_id_to_key = {}
|
|
443
430
|
self._space_key_to_id = {}
|
|
@@ -488,12 +475,7 @@ class ConfluenceSession:
|
|
|
488
475
|
self.session.close()
|
|
489
476
|
self.session = requests.Session()
|
|
490
477
|
|
|
491
|
-
def _build_url(
|
|
492
|
-
self,
|
|
493
|
-
version: ConfluenceVersion,
|
|
494
|
-
path: str,
|
|
495
|
-
query: dict[str, str] | None = None,
|
|
496
|
-
) -> str:
|
|
478
|
+
def _build_url(self, version: ConfluenceVersion, path: str, query: dict[str, str] | None = None) -> str:
|
|
497
479
|
"""
|
|
498
480
|
Builds a full URL for invoking the Confluence API.
|
|
499
481
|
|
|
@@ -506,14 +488,7 @@ class ConfluenceSession:
|
|
|
506
488
|
base_url = f"{self.api_url}{version.value}{path}"
|
|
507
489
|
return build_url(base_url, query)
|
|
508
490
|
|
|
509
|
-
def _get(
|
|
510
|
-
self,
|
|
511
|
-
version: ConfluenceVersion,
|
|
512
|
-
path: str,
|
|
513
|
-
response_type: type[T],
|
|
514
|
-
*,
|
|
515
|
-
query: dict[str, str] | None = None,
|
|
516
|
-
) -> T:
|
|
491
|
+
def _get(self, version: ConfluenceVersion, path: str, response_type: type[T], *, query: dict[str, str] | None = None) -> T:
|
|
517
492
|
"Executes an HTTP request via Confluence API."
|
|
518
493
|
|
|
519
494
|
url = self._build_url(version, path, query)
|
|
@@ -829,13 +804,7 @@ class ConfluenceSession:
|
|
|
829
804
|
LOGGER.info("Updating attachment: %s", attachment_id)
|
|
830
805
|
self._put(ConfluenceVersion.VERSION_1, path, request, None)
|
|
831
806
|
|
|
832
|
-
def get_page_properties_by_title(
|
|
833
|
-
self,
|
|
834
|
-
title: str,
|
|
835
|
-
*,
|
|
836
|
-
space_id: str | None = None,
|
|
837
|
-
space_key: str | None = None,
|
|
838
|
-
) -> ConfluencePageProperties:
|
|
807
|
+
def get_page_properties_by_title(self, title: str, *, space_id: str | None = None, space_key: str | None = None) -> ConfluencePageProperties:
|
|
839
808
|
"""
|
|
840
809
|
Looks up a Confluence wiki page ID by title.
|
|
841
810
|
|
|
@@ -890,10 +859,10 @@ class ConfluenceSession:
|
|
|
890
859
|
else:
|
|
891
860
|
raise
|
|
892
861
|
|
|
893
|
-
#
|
|
862
|
+
# this should not be reached, but satisfies type checker
|
|
894
863
|
if last_error is not None:
|
|
895
864
|
raise last_error
|
|
896
|
-
raise ConfluenceError(f"
|
|
865
|
+
raise ConfluenceError(f"failed to get page: {page_id}")
|
|
897
866
|
|
|
898
867
|
def get_page_properties(self, page_id: str) -> ConfluencePageProperties:
|
|
899
868
|
"""
|
|
@@ -916,14 +885,7 @@ class ConfluenceSession:
|
|
|
916
885
|
|
|
917
886
|
return self.get_page_properties(page_id).version.number
|
|
918
887
|
|
|
919
|
-
def update_page(
|
|
920
|
-
self,
|
|
921
|
-
page_id: str,
|
|
922
|
-
content: str,
|
|
923
|
-
*,
|
|
924
|
-
title: str,
|
|
925
|
-
version: int,
|
|
926
|
-
) -> None:
|
|
888
|
+
def update_page(self, page_id: str, content: str, *, title: str, version: int, message: str) -> None:
|
|
927
889
|
"""
|
|
928
890
|
Updates a page via the Confluence API.
|
|
929
891
|
|
|
@@ -939,35 +901,28 @@ class ConfluenceSession:
|
|
|
939
901
|
status=ConfluenceStatus.CURRENT,
|
|
940
902
|
title=title,
|
|
941
903
|
body=ConfluencePageBody(storage=ConfluencePageStorage(representation=ConfluenceRepresentation.STORAGE, value=content)),
|
|
942
|
-
version=ConfluenceContentVersion(number=version, minorEdit=True),
|
|
904
|
+
version=ConfluenceContentVersion(number=version, minorEdit=True, message=message),
|
|
943
905
|
)
|
|
944
906
|
LOGGER.info("Updating page: %s", page_id)
|
|
945
907
|
self._put(ConfluenceVersion.VERSION_2, path, request, None)
|
|
946
908
|
|
|
947
|
-
def create_page(
|
|
948
|
-
self,
|
|
949
|
-
parent_id: str,
|
|
950
|
-
title: str,
|
|
951
|
-
new_content: str,
|
|
952
|
-
) -> ConfluencePage:
|
|
909
|
+
def create_page(self, *, title: str, content: str, parent_id: str, space_id: str) -> ConfluencePage:
|
|
953
910
|
"""
|
|
954
911
|
Creates a new page via Confluence API.
|
|
955
912
|
"""
|
|
956
913
|
|
|
957
914
|
LOGGER.info("Creating page: %s", title)
|
|
958
915
|
|
|
959
|
-
parent_page = self.get_page_properties(parent_id)
|
|
960
|
-
|
|
961
916
|
path = "/pages/"
|
|
962
917
|
request = ConfluenceCreatePageRequest(
|
|
963
|
-
spaceId=
|
|
918
|
+
spaceId=space_id,
|
|
964
919
|
status=ConfluenceStatus.CURRENT,
|
|
965
920
|
title=title,
|
|
966
921
|
parentId=parent_id,
|
|
967
922
|
body=ConfluencePageBody(
|
|
968
923
|
storage=ConfluencePageStorage(
|
|
969
924
|
representation=ConfluenceRepresentation.STORAGE,
|
|
970
|
-
value=
|
|
925
|
+
value=content,
|
|
971
926
|
)
|
|
972
927
|
),
|
|
973
928
|
)
|
|
@@ -1009,23 +964,15 @@ class ConfluenceSession:
|
|
|
1009
964
|
response = self.session.delete(url, verify=True)
|
|
1010
965
|
response.raise_for_status()
|
|
1011
966
|
|
|
1012
|
-
def page_exists(
|
|
1013
|
-
self,
|
|
1014
|
-
title: str,
|
|
1015
|
-
*,
|
|
1016
|
-
space_id: str | None = None,
|
|
1017
|
-
space_key: str | None = None,
|
|
1018
|
-
) -> str | None:
|
|
967
|
+
def page_exists(self, title: str, *, space_id: str | None = None) -> str | None:
|
|
1019
968
|
"""
|
|
1020
969
|
Checks if a Confluence page exists with the given title.
|
|
1021
970
|
|
|
1022
971
|
:param title: Page title. Pages in the same Confluence space must have a unique title.
|
|
1023
|
-
:param
|
|
1024
|
-
|
|
972
|
+
:param space_id: Identifies the Confluence space.
|
|
1025
973
|
:returns: Confluence page ID of a matching page (if found), or `None`.
|
|
1026
974
|
"""
|
|
1027
975
|
|
|
1028
|
-
space_id = self._get_space_id(space_id=space_id, space_key=space_key)
|
|
1029
976
|
path = "/pages"
|
|
1030
977
|
query = {"title": title}
|
|
1031
978
|
if space_id is not None:
|
|
@@ -1058,17 +1005,19 @@ class ConfluenceSession:
|
|
|
1058
1005
|
|
|
1059
1006
|
:param title: Page title. Pages in the same Confluence space must have a unique title.
|
|
1060
1007
|
:param parent_id: Identifies the parent page for a new child page.
|
|
1008
|
+
:returns: Confluence page info for the found or newly created page.
|
|
1061
1009
|
"""
|
|
1062
1010
|
|
|
1063
1011
|
parent_page = self.get_page_properties(parent_id)
|
|
1064
|
-
|
|
1012
|
+
space_id = parent_page.spaceId
|
|
1013
|
+
page_id = self.page_exists(title, space_id=space_id)
|
|
1065
1014
|
|
|
1066
1015
|
if page_id is not None:
|
|
1067
1016
|
LOGGER.debug("Retrieving existing page: %s", page_id)
|
|
1068
1017
|
return self.get_page(page_id)
|
|
1069
1018
|
else:
|
|
1070
1019
|
LOGGER.debug("Creating new page with title: %s", title)
|
|
1071
|
-
return self.create_page(
|
|
1020
|
+
return self.create_page(title=title, content="", parent_id=parent_id, space_id=space_id)
|
|
1072
1021
|
|
|
1073
1022
|
def get_labels(self, page_id: str) -> list[ConfluenceIdentifiedLabel]:
|
|
1074
1023
|
"""
|
|
@@ -1132,6 +1081,23 @@ class ConfluenceSession:
|
|
|
1132
1081
|
remove_labels.sort()
|
|
1133
1082
|
self.remove_labels(page_id, remove_labels)
|
|
1134
1083
|
|
|
1084
|
+
def get_content_property_for_page(self, page_id: str, key: str) -> ConfluenceIdentifiedContentProperty | None:
|
|
1085
|
+
"""
|
|
1086
|
+
Retrieves a content property for a Confluence page.
|
|
1087
|
+
|
|
1088
|
+
:param page_id: The Confluence page ID.
|
|
1089
|
+
:param key: The name of the property to fetch (with case-sensitive match).
|
|
1090
|
+
:returns: The content property value, or `None` if not found.
|
|
1091
|
+
"""
|
|
1092
|
+
|
|
1093
|
+
path = f"/pages/{page_id}/properties"
|
|
1094
|
+
results = self._fetch(path, query={"key": key})
|
|
1095
|
+
properties = json_to_object(list[ConfluenceIdentifiedContentProperty], results)
|
|
1096
|
+
if len(properties) == 1:
|
|
1097
|
+
return properties.pop()
|
|
1098
|
+
else:
|
|
1099
|
+
return None
|
|
1100
|
+
|
|
1135
1101
|
def get_content_properties_for_page(self, page_id: str) -> list[ConfluenceIdentifiedContentProperty]:
|
|
1136
1102
|
"""
|
|
1137
1103
|
Retrieves content properties for a Confluence page.
|
md2conf/attachment.py
CHANGED
|
@@ -40,6 +40,9 @@ class AttachmentCatalog:
|
|
|
40
40
|
self.embedded_files[filename] = data
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
_DISALLOWED_CHAR_REGEXP = re.compile(r"[^\-0-9A-Za-z_.]", re.UNICODE)
|
|
44
|
+
|
|
45
|
+
|
|
43
46
|
def attachment_name(ref: Path | str) -> str:
|
|
44
47
|
"""
|
|
45
48
|
Safe name for use with attachment uploads.
|
|
@@ -60,13 +63,11 @@ def attachment_name(ref: Path | str) -> str:
|
|
|
60
63
|
if path.drive or path.root:
|
|
61
64
|
raise ValueError(f"required: relative path; got: {ref}")
|
|
62
65
|
|
|
63
|
-
regexp = re.compile(r"[^\-0-9A-Za-z_.]", re.UNICODE)
|
|
64
|
-
|
|
65
66
|
def replace_part(part: str) -> str:
|
|
66
67
|
if part == "..":
|
|
67
68
|
return "PAR"
|
|
68
69
|
else:
|
|
69
|
-
return
|
|
70
|
+
return _DISALLOWED_CHAR_REGEXP.sub("_", part)
|
|
70
71
|
|
|
71
72
|
parts = [replace_part(p) for p in path.parts]
|
|
72
73
|
return Path(*parts).as_posix().replace("/", "_")
|