markdown-to-confluence 0.5.0__py3-none-any.whl → 0.5.2__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.0.dist-info → markdown_to_confluence-0.5.2.dist-info}/METADATA +83 -9
- {markdown_to_confluence-0.5.0.dist-info → markdown_to_confluence-0.5.2.dist-info}/RECORD +17 -16
- md2conf/__init__.py +1 -1
- md2conf/__main__.py +56 -9
- md2conf/api.py +28 -2
- md2conf/converter.py +282 -38
- md2conf/domain.py +10 -3
- md2conf/latex.py +4 -4
- md2conf/publisher.py +3 -0
- md2conf/scanner.py +2 -2
- md2conf/serializer.py +14 -2
- md2conf/svg.py +319 -0
- {markdown_to_confluence-0.5.0.dist-info → markdown_to_confluence-0.5.2.dist-info}/WHEEL +0 -0
- {markdown_to_confluence-0.5.0.dist-info → markdown_to_confluence-0.5.2.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.5.0.dist-info → markdown_to_confluence-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {markdown_to_confluence-0.5.0.dist-info → markdown_to_confluence-0.5.2.dist-info}/top_level.txt +0 -0
- {markdown_to_confluence-0.5.0.dist-info → markdown_to_confluence-0.5.2.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.5.
|
|
3
|
+
Version: 0.5.2
|
|
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>
|
|
@@ -26,18 +26,19 @@ License-File: LICENSE
|
|
|
26
26
|
Requires-Dist: cattrs>=25.3
|
|
27
27
|
Requires-Dist: lxml>=6.0
|
|
28
28
|
Requires-Dist: markdown>=3.10
|
|
29
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: orjson>=3.11
|
|
30
|
+
Requires-Dist: pymdown-extensions>=10.19
|
|
30
31
|
Requires-Dist: PyYAML>=6.0
|
|
31
32
|
Requires-Dist: requests>=2.32
|
|
32
33
|
Requires-Dist: truststore>=0.10
|
|
33
34
|
Requires-Dist: typing-extensions>=4.15; python_version < "3.12"
|
|
34
35
|
Provides-Extra: dev
|
|
35
|
-
Requires-Dist: markdown_doc>=0.1.
|
|
36
|
-
Requires-Dist: types-lxml>=2025.
|
|
36
|
+
Requires-Dist: markdown_doc>=0.1.6; extra == "dev"
|
|
37
|
+
Requires-Dist: types-lxml>=2025.11.25; extra == "dev"
|
|
37
38
|
Requires-Dist: types-markdown>=3.10; extra == "dev"
|
|
38
39
|
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
39
40
|
Requires-Dist: types-requests>=2.32; extra == "dev"
|
|
40
|
-
Requires-Dist: mypy>=1.
|
|
41
|
+
Requires-Dist: mypy>=1.19; extra == "dev"
|
|
41
42
|
Requires-Dist: ruff>=0.14; extra == "dev"
|
|
42
43
|
Provides-Extra: formulas
|
|
43
44
|
Requires-Dist: matplotlib>=3.9; extra == "formulas"
|
|
@@ -214,6 +215,19 @@ Provide generated-by prompt text in the Markdown file with a tag:
|
|
|
214
215
|
|
|
215
216
|
Alternatively, use the `--generated-by GENERATED_BY` option. The tag takes precedence.
|
|
216
217
|
|
|
218
|
+
The generated-by text can also be templated with the following variables:
|
|
219
|
+
|
|
220
|
+
- `%{filename}`: the name of the Markdown file
|
|
221
|
+
- `%{filepath}`: the path of the Markdown file relative to the *source root*
|
|
222
|
+
|
|
223
|
+
When publishing a directory hierarchy, the *source root* is the directory in which *md2conf* is launched. When publishing a single file, this is the directory in which the Markdown file resides.
|
|
224
|
+
|
|
225
|
+
It can be used with the CLI `--generated-by` option or directly in the files:
|
|
226
|
+
|
|
227
|
+
```markdown
|
|
228
|
+
<!-- generated-by: Do not edit! Check out the file %{filepath} in the repo -->
|
|
229
|
+
```
|
|
230
|
+
|
|
217
231
|
### Publishing a single page
|
|
218
232
|
|
|
219
233
|
*md2conf* has two modes of operation: *single-page mode* and *directory mode*.
|
|
@@ -454,6 +468,58 @@ This is useful if you have a page in a hierarchy that participates in parent-chi
|
|
|
454
468
|
|
|
455
469
|
If the `title` attribute (in the front-matter) or the topmost unique heading (in the document body) changes, the Confluence page title is updated. A warning is raised if the new title conflicts with the title of another page, and thus cannot be updated.
|
|
456
470
|
|
|
471
|
+
#### Avoiding duplicate titles
|
|
472
|
+
|
|
473
|
+
By default, when *md2conf* extracts a page title from the first unique heading in a Markdown document, the heading remains in the document body. This means the title appears twice on the Confluence page: once as the page title at the top, and once as the first heading in the content.
|
|
474
|
+
|
|
475
|
+
To avoid this duplication, use the `--skip-title-heading` option. When enabled, *md2conf* removes the first heading from the document body if it was used as the page title. This option only takes effect when:
|
|
476
|
+
|
|
477
|
+
1. The title was extracted from the document's first unique heading (not from front-matter), AND
|
|
478
|
+
2. There is exactly one top-level heading in the document.
|
|
479
|
+
|
|
480
|
+
If the title comes from the `title` attribute in front-matter, the heading is preserved in the document body regardless of this setting, as the heading and title are considered separate.
|
|
481
|
+
|
|
482
|
+
**Example without `--skip-title-heading` (default):**
|
|
483
|
+
|
|
484
|
+
Markdown:
|
|
485
|
+
```markdown
|
|
486
|
+
# Installation Guide
|
|
487
|
+
|
|
488
|
+
Follow these steps...
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Confluence displays:
|
|
492
|
+
- Page title: "Installation Guide"
|
|
493
|
+
- Content: Starts with heading "Installation Guide", followed by "Follow these steps..."
|
|
494
|
+
|
|
495
|
+
**Example with `--skip-title-heading`:**
|
|
496
|
+
|
|
497
|
+
Same Markdown source, but Confluence displays:
|
|
498
|
+
- Page title: "Installation Guide"
|
|
499
|
+
- Content: Starts directly with "Follow these steps..." (heading removed)
|
|
500
|
+
|
|
501
|
+
**Edge case: Abstract or introductory text before the title:**
|
|
502
|
+
|
|
503
|
+
When a document has content before the first heading (like an abstract), removing the heading eliminates the visual separator between the introductory text and the main content:
|
|
504
|
+
|
|
505
|
+
```markdown
|
|
506
|
+
This is an abstract paragraph providing context.
|
|
507
|
+
|
|
508
|
+
# Document Title
|
|
509
|
+
|
|
510
|
+
This is the main document content.
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
With `--skip-title-heading`, the output becomes:
|
|
514
|
+
- Page title: "Document Title"
|
|
515
|
+
- Content: "This is an abstract paragraph..." flows directly into "This is the main document content..." (no heading separator)
|
|
516
|
+
|
|
517
|
+
While the structure remains semantically correct, the visual separation is lost. If you need to maintain separation, consider these workarounds:
|
|
518
|
+
|
|
519
|
+
1. **Use a horizontal rule:** Add `---` after the abstract to create visual separation
|
|
520
|
+
2. **Use an admonition block:** Wrap the abstract in an info/note block
|
|
521
|
+
3. **Use front-matter title:** Set `title` in front-matter to keep the heading in the body
|
|
522
|
+
|
|
457
523
|
### Labels
|
|
458
524
|
|
|
459
525
|
If a Markdown document has the front-matter attribute `tags`, *md2conf* assigns the specified tags to the Confluence page as labels.
|
|
@@ -570,7 +636,7 @@ options:
|
|
|
570
636
|
-r ROOT_PAGE Root Confluence page to create new pages. If omitted, will raise exception when creating new pages.
|
|
571
637
|
--keep-hierarchy Maintain source directory structure when exporting to Confluence.
|
|
572
638
|
--flatten-hierarchy Flatten directories with no index.md or README.md when exporting to Confluence.
|
|
573
|
-
--generated-by
|
|
639
|
+
--generated-by MARKDOWN
|
|
574
640
|
Add prompt to pages (default: 'This page has been generated with a tool.').
|
|
575
641
|
--no-generated-by Do not add 'generated by a tool' prompt to pages.
|
|
576
642
|
--render-drawio Render draw.io diagrams as image files. (Installed utility required to covert.)
|
|
@@ -581,16 +647,24 @@ options:
|
|
|
581
647
|
--no-render-latex Inline LaTeX formulas in Confluence page. (Marketplace app required to display.)
|
|
582
648
|
--diagram-output-format {png,svg}
|
|
583
649
|
Format for rendering Mermaid and draw.io diagrams (default: 'png').
|
|
650
|
+
--prefer-raster Prefer PNG over SVG when both exist (default: enabled).
|
|
651
|
+
--no-prefer-raster Use SVG files directly instead of preferring PNG equivalents.
|
|
584
652
|
--heading-anchors Place an anchor at each section heading with GitHub-style same-page identifiers.
|
|
585
653
|
--no-heading-anchors Don't place an anchor at each section heading.
|
|
586
654
|
--ignore-invalid-url Emit a warning but otherwise ignore relative URLs that point to ill-specified locations.
|
|
587
|
-
--
|
|
588
|
-
--
|
|
589
|
-
|
|
655
|
+
--skip-title-heading Skip the first heading from document body when it is used as the page title (does not apply if title comes from front-matter).
|
|
656
|
+
--no-skip-title-heading
|
|
657
|
+
Keep the first heading in document body even when used as page title (default).
|
|
658
|
+
--title-prefix TEXT String to prepend to Confluence page title for each published page.
|
|
590
659
|
--webui-links Enable Confluence Web UI links. (Typically required for on-prem versions of Confluence.)
|
|
591
660
|
--alignment {center,left,right}
|
|
592
661
|
Alignment for block-level images and formulas (default: 'center').
|
|
662
|
+
--max-image-width MAX_IMAGE_WIDTH
|
|
663
|
+
Maximum display width for images [px]. Wider images are scaled down for page display. Original size kept for full-size viewing.
|
|
593
664
|
--use-panel Transform admonitions and alerts into a Confluence custom panel.
|
|
665
|
+
--local Write XHTML-based Confluence Storage Format files locally without invoking Confluence API.
|
|
666
|
+
--headers KEY=VALUE [KEY=VALUE ...]
|
|
667
|
+
Apply custom headers to all Confluence API requests.
|
|
594
668
|
```
|
|
595
669
|
|
|
596
670
|
### Confluence REST API v1 vs. v2
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
-
markdown_to_confluence-0.5.
|
|
2
|
-
md2conf/__init__.py,sha256=
|
|
3
|
-
md2conf/__main__.py,sha256=
|
|
4
|
-
md2conf/api.py,sha256=
|
|
1
|
+
markdown_to_confluence-0.5.2.dist-info/licenses/LICENSE,sha256=56L-Y0dyZwyVlINRJRz3PNw-ka-oLVaAq-7d8zo6qlc,1077
|
|
2
|
+
md2conf/__init__.py,sha256=TZU4q64xgFKmFudp0-NIfMbAgcFihMCih-5sjAybGKs,402
|
|
3
|
+
md2conf/__main__.py,sha256=GdYd7v6YpIbOcvaUNh_5TZXwnEGbaxlsIS0sJtMmyhE,13152
|
|
4
|
+
md2conf/api.py,sha256=v4QXiNFGHyIhYZWb36uG-AR4HBPoj2GZI939ao9SOIQ,41989
|
|
5
5
|
md2conf/collection.py,sha256=nghFS5kK4kPbpLE7IHi4rprJK-Mu4KXNxjHYM9Rc5SQ,824
|
|
6
|
-
md2conf/converter.py,sha256=
|
|
6
|
+
md2conf/converter.py,sha256=S6JiC-v5jqEVv4lcvCKxfLO-dZpHNLEfuocFBslIpaA,79924
|
|
7
7
|
md2conf/csf.py,sha256=rugs3qC2aJQCJSTczeBw9WhqSZZtMq14LjwT0V1b6Hc,6476
|
|
8
|
-
md2conf/domain.py,sha256=
|
|
8
|
+
md2conf/domain.py,sha256=opq_O_NOz097HC4Q8VA7aNba6Snq09euTCi-Ag-bvAo,2685
|
|
9
9
|
md2conf/drawio.py,sha256=IqFlAegrKM5SQf5CqHD8STIzskH7Rpm9RtWwn_nXVTc,8581
|
|
10
10
|
md2conf/emoticon.py,sha256=P2L5oQvnRXeVifJQ3sJ2Ck-6ptbxumq2vsT-rM0W0Ms,484
|
|
11
11
|
md2conf/entities.dtd,sha256=M6NzqL5N7dPs_eUA_6sDsiSLzDaAacrx9LdttiufvYU,30215
|
|
12
12
|
md2conf/environment.py,sha256=BhI7YktY7G26HOhGlUvTkH2Vmfa4E_dhu2snzbBgMvE,3902
|
|
13
13
|
md2conf/extra.py,sha256=VuMxuOnnC2Qwy6y52ukIxsaYhrZArRqMmRHRE4QZl8g,687
|
|
14
|
-
md2conf/latex.py,sha256=
|
|
14
|
+
md2conf/latex.py,sha256=vZJakhwiSPkprz5IkJZOUw9H4FVXj_kksgMKoF8N_pI,7747
|
|
15
15
|
md2conf/local.py,sha256=Ou-j7kZWbHxC8Si8Yg7myqtTQ1He6mYQW1NpX3LLIcY,3704
|
|
16
16
|
md2conf/markdown.py,sha256=t-z19Zs_91_jzRvwmOsWqCDt0Tdghmk5bpNUON0YlKc,3148
|
|
17
17
|
md2conf/matcher.py,sha256=hkM49osFM9nrXRXe4pwcGCg0rrLsmKep7AYY_S01kNY,6774
|
|
18
18
|
md2conf/mermaid.py,sha256=9P4VV69dooaFBNUjdTIpzq7BFA8rDMqEif1O7XKWPdM,2617
|
|
19
19
|
md2conf/metadata.py,sha256=_kt_lh4gCzVRRhhrDk-cJCk9WMcX9ZDWB6hL0Lw3xoI,976
|
|
20
20
|
md2conf/processor.py,sha256=8Y-NSxAuqSHMSN9vhw_83HisGAmq87XAY98dis_xZ0Y,9690
|
|
21
|
-
md2conf/publisher.py,sha256=
|
|
21
|
+
md2conf/publisher.py,sha256=ma0E_Kcxd86Qe_ywdhiTydkoJFRp6MI_Hy2ImFMKWsA,8752
|
|
22
22
|
md2conf/puppeteer-config.json,sha256=-dMTAN_7kNTGbDlfXzApl0KJpAWna9YKZdwMKbpOb60,159
|
|
23
23
|
md2conf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
-
md2conf/scanner.py,sha256=
|
|
25
|
-
md2conf/serializer.py,sha256=
|
|
24
|
+
md2conf/scanner.py,sha256=xxzNm3IRnlS0yAkStTHCigeLd6hUyvMYIBKFpTBEPf4,6776
|
|
25
|
+
md2conf/serializer.py,sha256=JrBj8Z9zP8PjBeVAlRRqnMKDoz6IvkRbTd6K-JgFVow,1757
|
|
26
|
+
md2conf/svg.py,sha256=LImoEKerVdXGkTnR6SogKvsR7WJNDVvMN1Ju_usbrNs,10306
|
|
26
27
|
md2conf/text.py,sha256=fHOrUaPXAjE4iRhHqFq-CiI-knpo4wvyHCWp0crewqA,1736
|
|
27
28
|
md2conf/toc.py,sha256=ZrfUfTv_Jiv27G4SBNjK3b-1ClYKoqN5yPRsEWp6IXk,2413
|
|
28
29
|
md2conf/uri.py,sha256=KbLBdRFtZTQTZd8b4j0LtE8Pb68Ly0WkemF4iW-EAB4,1158
|
|
29
30
|
md2conf/xml.py,sha256=Fu00Eg8c0VgMHIjRDBJBSNWtui8obEtowkiR7gHTduM,5526
|
|
30
|
-
markdown_to_confluence-0.5.
|
|
31
|
-
markdown_to_confluence-0.5.
|
|
32
|
-
markdown_to_confluence-0.5.
|
|
33
|
-
markdown_to_confluence-0.5.
|
|
34
|
-
markdown_to_confluence-0.5.
|
|
35
|
-
markdown_to_confluence-0.5.
|
|
31
|
+
markdown_to_confluence-0.5.2.dist-info/METADATA,sha256=fts0cqy6A_o_QztqMmfVGWoY1kLEDftLNPKGhPsoMQo,40071
|
|
32
|
+
markdown_to_confluence-0.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
33
|
+
markdown_to_confluence-0.5.2.dist-info/entry_points.txt,sha256=F1zxa1wtEObtbHS-qp46330WVFLHdMnV2wQ-ZorRmX0,50
|
|
34
|
+
markdown_to_confluence-0.5.2.dist-info/top_level.txt,sha256=_FJfl_kHrHNidyjUOuS01ngu_jDsfc-ZjSocNRJnTzU,8
|
|
35
|
+
markdown_to_confluence-0.5.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
36
|
+
markdown_to_confluence-0.5.2.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.2"
|
|
9
9
|
__author__ = "Levente Hunyadi"
|
|
10
10
|
__copyright__ = "Copyright 2022-2025, Levente Hunyadi"
|
|
11
11
|
__license__ = "MIT"
|
md2conf/__main__.py
CHANGED
|
@@ -34,11 +34,14 @@ class Arguments(argparse.Namespace):
|
|
|
34
34
|
api_key: str | None
|
|
35
35
|
space: str | None
|
|
36
36
|
loglevel: str
|
|
37
|
-
ignore_invalid_url: bool
|
|
38
37
|
heading_anchors: bool
|
|
38
|
+
ignore_invalid_url: bool
|
|
39
39
|
root_page: str | None
|
|
40
40
|
keep_hierarchy: bool
|
|
41
|
+
skip_title_heading: bool
|
|
42
|
+
title_prefix: str | None
|
|
41
43
|
generated_by: str | None
|
|
44
|
+
prefer_raster: bool
|
|
42
45
|
render_drawio: bool
|
|
43
46
|
render_mermaid: bool
|
|
44
47
|
render_latex: bool
|
|
@@ -47,6 +50,7 @@ class Arguments(argparse.Namespace):
|
|
|
47
50
|
headers: dict[str, str]
|
|
48
51
|
webui_links: bool
|
|
49
52
|
alignment: Literal["center", "left", "right"]
|
|
53
|
+
max_image_width: int | None
|
|
50
54
|
use_panel: bool
|
|
51
55
|
|
|
52
56
|
|
|
@@ -151,6 +155,7 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
151
155
|
parser.add_argument(
|
|
152
156
|
"--generated-by",
|
|
153
157
|
default="This page has been generated with a tool.",
|
|
158
|
+
metavar="MARKDOWN",
|
|
154
159
|
help="Add prompt to pages (default: 'This page has been generated with a tool.').",
|
|
155
160
|
)
|
|
156
161
|
parser.add_argument(
|
|
@@ -206,6 +211,19 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
206
211
|
default="png",
|
|
207
212
|
help="Format for rendering Mermaid and draw.io diagrams (default: 'png').",
|
|
208
213
|
)
|
|
214
|
+
parser.add_argument(
|
|
215
|
+
"--prefer-raster",
|
|
216
|
+
dest="prefer_raster",
|
|
217
|
+
action="store_true",
|
|
218
|
+
default=True,
|
|
219
|
+
help="Prefer PNG over SVG when both exist (default: enabled).",
|
|
220
|
+
)
|
|
221
|
+
parser.add_argument(
|
|
222
|
+
"--no-prefer-raster",
|
|
223
|
+
dest="prefer_raster",
|
|
224
|
+
action="store_false",
|
|
225
|
+
help="Use SVG files directly instead of preferring PNG equivalents.",
|
|
226
|
+
)
|
|
209
227
|
parser.add_argument(
|
|
210
228
|
"--heading-anchors",
|
|
211
229
|
action="store_true",
|
|
@@ -225,18 +243,22 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
225
243
|
help="Emit a warning but otherwise ignore relative URLs that point to ill-specified locations.",
|
|
226
244
|
)
|
|
227
245
|
parser.add_argument(
|
|
228
|
-
"--
|
|
246
|
+
"--skip-title-heading",
|
|
229
247
|
action="store_true",
|
|
230
248
|
default=False,
|
|
231
|
-
help="
|
|
249
|
+
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).",
|
|
232
250
|
)
|
|
233
251
|
parser.add_argument(
|
|
234
|
-
"--
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
252
|
+
"--no-skip-title-heading",
|
|
253
|
+
dest="skip_title_heading",
|
|
254
|
+
action="store_false",
|
|
255
|
+
help="Keep the first heading in document body even when used as page title (default).",
|
|
256
|
+
)
|
|
257
|
+
parser.add_argument(
|
|
258
|
+
"--title-prefix",
|
|
259
|
+
default=None,
|
|
260
|
+
metavar="TEXT",
|
|
261
|
+
help="String to prepend to Confluence page title for each published page.",
|
|
240
262
|
)
|
|
241
263
|
parser.add_argument(
|
|
242
264
|
"--webui-links",
|
|
@@ -251,12 +273,33 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
251
273
|
default="center",
|
|
252
274
|
help="Alignment for block-level images and formulas (default: 'center').",
|
|
253
275
|
)
|
|
276
|
+
parser.add_argument(
|
|
277
|
+
"--max-image-width",
|
|
278
|
+
dest="max_image_width",
|
|
279
|
+
type=int,
|
|
280
|
+
default=None,
|
|
281
|
+
help="Maximum display width for images [px]. Wider images are scaled down for page display. Original size kept for full-size viewing.",
|
|
282
|
+
)
|
|
254
283
|
parser.add_argument(
|
|
255
284
|
"--use-panel",
|
|
256
285
|
action="store_true",
|
|
257
286
|
default=False,
|
|
258
287
|
help="Transform admonitions and alerts into a Confluence custom panel.",
|
|
259
288
|
)
|
|
289
|
+
parser.add_argument(
|
|
290
|
+
"--local",
|
|
291
|
+
action="store_true",
|
|
292
|
+
default=False,
|
|
293
|
+
help="Write XHTML-based Confluence Storage Format files locally without invoking Confluence API.",
|
|
294
|
+
)
|
|
295
|
+
parser.add_argument(
|
|
296
|
+
"--headers",
|
|
297
|
+
nargs="+",
|
|
298
|
+
required=False,
|
|
299
|
+
action=KwargsAppendAction,
|
|
300
|
+
metavar="KEY=VALUE",
|
|
301
|
+
help="Apply custom headers to all Confluence API requests.",
|
|
302
|
+
)
|
|
260
303
|
return parser
|
|
261
304
|
|
|
262
305
|
|
|
@@ -282,15 +325,19 @@ def main() -> None:
|
|
|
282
325
|
options = ConfluenceDocumentOptions(
|
|
283
326
|
heading_anchors=args.heading_anchors,
|
|
284
327
|
ignore_invalid_url=args.ignore_invalid_url,
|
|
328
|
+
skip_title_heading=args.skip_title_heading,
|
|
329
|
+
title_prefix=args.title_prefix,
|
|
285
330
|
generated_by=args.generated_by,
|
|
286
331
|
root_page_id=ConfluencePageID(args.root_page) if args.root_page else None,
|
|
287
332
|
keep_hierarchy=args.keep_hierarchy,
|
|
333
|
+
prefer_raster=args.prefer_raster,
|
|
288
334
|
render_drawio=args.render_drawio,
|
|
289
335
|
render_mermaid=args.render_mermaid,
|
|
290
336
|
render_latex=args.render_latex,
|
|
291
337
|
diagram_output_format=args.diagram_output_format,
|
|
292
338
|
webui_links=args.webui_links,
|
|
293
339
|
alignment=args.alignment,
|
|
340
|
+
max_image_width=args.max_image_width,
|
|
294
341
|
use_panel=args.use_panel,
|
|
295
342
|
)
|
|
296
343
|
if args.local:
|
md2conf/api.py
CHANGED
|
@@ -11,7 +11,9 @@ import enum
|
|
|
11
11
|
import io
|
|
12
12
|
import logging
|
|
13
13
|
import mimetypes
|
|
14
|
+
import random
|
|
14
15
|
import ssl
|
|
16
|
+
import time
|
|
15
17
|
import typing
|
|
16
18
|
from dataclasses import dataclass
|
|
17
19
|
from pathlib import Path
|
|
@@ -30,6 +32,7 @@ from .serializer import JsonType, json_to_object, object_to_json_payload
|
|
|
30
32
|
|
|
31
33
|
T = TypeVar("T")
|
|
32
34
|
|
|
35
|
+
# spellchecker: disable
|
|
33
36
|
mimetypes.add_type("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx", strict=True)
|
|
34
37
|
mimetypes.add_type("text/vnd.mermaid", ".mmd", strict=True)
|
|
35
38
|
mimetypes.add_type("application/vnd.oasis.opendocument.presentation", ".odp", strict=True)
|
|
@@ -37,6 +40,7 @@ mimetypes.add_type("application/vnd.oasis.opendocument.spreadsheet", ".ods", str
|
|
|
37
40
|
mimetypes.add_type("application/vnd.oasis.opendocument.text", ".odt", strict=True)
|
|
38
41
|
mimetypes.add_type("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx", strict=True)
|
|
39
42
|
mimetypes.add_type("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx", strict=True)
|
|
43
|
+
# spellchecker: enable
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
def build_url(base_url: str, query: dict[str, str] | None = None) -> str:
|
|
@@ -839,16 +843,38 @@ class ConfluenceSession:
|
|
|
839
843
|
page = json_to_object(ConfluencePageProperties, results[0])
|
|
840
844
|
return page
|
|
841
845
|
|
|
842
|
-
def get_page(self, page_id: str) -> ConfluencePage:
|
|
846
|
+
def get_page(self, page_id: str, *, retries: int = 3, retry_delay: float = 1.0) -> ConfluencePage:
|
|
843
847
|
"""
|
|
844
848
|
Retrieves Confluence wiki page details and content.
|
|
845
849
|
|
|
850
|
+
Includes retry logic to handle eventual consistency issues when fetching
|
|
851
|
+
a newly created page that may not be immediately available via the API.
|
|
852
|
+
|
|
846
853
|
:param page_id: The Confluence page ID.
|
|
854
|
+
:param retries: Number of retry attempts for 404 errors (default: 3).
|
|
855
|
+
:param retry_delay: Initial delay in seconds between retries, doubles each attempt (default: 1.0).
|
|
847
856
|
:returns: Confluence page info and content.
|
|
848
857
|
"""
|
|
849
858
|
|
|
850
859
|
path = f"/pages/{page_id}"
|
|
851
|
-
|
|
860
|
+
last_error: requests.HTTPError | None = None
|
|
861
|
+
|
|
862
|
+
for attempt in range(retries + 1):
|
|
863
|
+
try:
|
|
864
|
+
return self._get(ConfluenceVersion.VERSION_2, path, ConfluencePage, query={"body-format": "storage"})
|
|
865
|
+
except requests.HTTPError as e:
|
|
866
|
+
if e.response is not None and e.response.status_code == 404 and attempt < retries:
|
|
867
|
+
delay = retry_delay * (2**attempt) + random.uniform(0, 1)
|
|
868
|
+
LOGGER.debug("Page %s not found, retrying in %.1f seconds (attempt %d/%d)", page_id, delay, attempt + 1, retries)
|
|
869
|
+
time.sleep(delay)
|
|
870
|
+
last_error = e
|
|
871
|
+
else:
|
|
872
|
+
raise
|
|
873
|
+
|
|
874
|
+
# This should not be reached, but satisfies type checker
|
|
875
|
+
if last_error is not None:
|
|
876
|
+
raise last_error
|
|
877
|
+
raise ConfluenceError(f"Failed to get page {page_id}")
|
|
852
878
|
|
|
853
879
|
def get_page_properties(self, page_id: str) -> ConfluencePageProperties:
|
|
854
880
|
"""
|