markdown-to-confluence 0.5.2__py3-none-any.whl → 0.5.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.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/METADATA +80 -4
- markdown_to_confluence-0.5.3.dist-info/RECORD +55 -0
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/licenses/LICENSE +1 -1
- md2conf/__init__.py +2 -2
- md2conf/__main__.py +42 -24
- md2conf/api.py +27 -8
- md2conf/attachment.py +72 -0
- md2conf/coalesce.py +43 -0
- md2conf/collection.py +1 -1
- md2conf/{extra.py → compatibility.py} +1 -1
- md2conf/converter.py +232 -649
- md2conf/csf.py +13 -11
- md2conf/drawio/__init__.py +0 -0
- md2conf/drawio/extension.py +116 -0
- md2conf/{drawio.py → drawio/render.py} +1 -1
- md2conf/emoticon.py +3 -3
- md2conf/environment.py +2 -2
- md2conf/extension.py +78 -0
- md2conf/external.py +49 -0
- md2conf/formatting.py +135 -0
- md2conf/frontmatter.py +70 -0
- md2conf/image.py +127 -0
- md2conf/latex.py +4 -183
- md2conf/local.py +8 -8
- md2conf/markdown.py +1 -1
- md2conf/matcher.py +1 -1
- md2conf/mermaid/__init__.py +0 -0
- md2conf/mermaid/config.py +20 -0
- md2conf/mermaid/extension.py +109 -0
- md2conf/{mermaid.py → mermaid/render.py} +10 -38
- md2conf/mermaid/scanner.py +55 -0
- md2conf/metadata.py +1 -1
- md2conf/{domain.py → options.py} +73 -16
- md2conf/plantuml/__init__.py +0 -0
- md2conf/plantuml/config.py +20 -0
- md2conf/plantuml/extension.py +158 -0
- md2conf/plantuml/render.py +139 -0
- md2conf/plantuml/scanner.py +56 -0
- md2conf/png.py +202 -0
- md2conf/processor.py +32 -11
- md2conf/publisher.py +14 -18
- md2conf/scanner.py +31 -128
- md2conf/serializer.py +2 -2
- md2conf/svg.py +24 -2
- md2conf/text.py +1 -1
- md2conf/toc.py +1 -1
- md2conf/uri.py +1 -1
- md2conf/xml.py +1 -1
- markdown_to_confluence-0.5.2.dist-info/RECORD +0 -36
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/WHEEL +0 -0
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/top_level.txt +0 -0
- {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.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.5.
|
|
3
|
+
Version: 0.5.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>
|
|
@@ -98,6 +98,15 @@ pip install markdown-to-confluence
|
|
|
98
98
|
npm install -g @mermaid-js/mermaid-cli
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
**Optional.** Pre-rendering PlantUML diagrams into PNG or SVG images requires Java, Graphviz and [PlantUML](https://plantuml.com/). (Refer to `--render-plantuml`.)
|
|
102
|
+
|
|
103
|
+
1. **Install Java**: Version 8 or later from [Adoptium](https://adoptium.net/) or [Oracle](https://www.oracle.com/java/technologies/downloads/)
|
|
104
|
+
2. **Install Graphviz**: Required for most diagram types in PlantUML (except sequence diagrams)
|
|
105
|
+
* **Ubuntu/Debian**: `sudo apt-get install Graphviz`
|
|
106
|
+
* **macOS**: `brew install graphviz`
|
|
107
|
+
* **Windows**: Download from [graphviz.org](https://graphviz.org/download/)
|
|
108
|
+
3. **Download PlantUML JAR**: Download [plantuml.jar](https://github.com/plantuml/plantuml/releases) and set `PLANTUML_JAR` environment variable to point to it
|
|
109
|
+
|
|
101
110
|
**Optional.** Converting formulas and equations to PNG or SVG images requires [Matplotlib](https://matplotlib.org/):
|
|
102
111
|
|
|
103
112
|
```sh
|
|
@@ -112,7 +121,11 @@ As authors of *md2conf*, we don't endorse or support any particular Confluence m
|
|
|
112
121
|
|
|
113
122
|
**Optional.** Displaying Mermaid diagrams in Confluence without pre-rendering in the synchronization phase requires a [marketplace app](https://marketplace.atlassian.com/apps/1226567/mermaid-diagrams-for-confluence). (Refer to `--no-render-mermaid`.)
|
|
114
123
|
|
|
115
|
-
**Optional.**
|
|
124
|
+
**Optional.** PlantUML diagrams are embedded with compressed source data and are displayed using the [PlantUML Diagrams for Confluence](https://marketplace.atlassian.com/apps/1215115/plantuml-diagrams-for-confluence) app (if installed). (Refer to `--no-render-plantuml`.)
|
|
125
|
+
|
|
126
|
+
Installing `plantuml.jar` (see above) helps display embedded diagrams with pre-calculated optimal dimensions.
|
|
127
|
+
|
|
128
|
+
**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/). (Refer to `--no-render-latex`.)
|
|
116
129
|
|
|
117
130
|
## Getting started
|
|
118
131
|
|
|
@@ -218,7 +231,9 @@ Alternatively, use the `--generated-by GENERATED_BY` option. The tag takes prece
|
|
|
218
231
|
The generated-by text can also be templated with the following variables:
|
|
219
232
|
|
|
220
233
|
- `%{filename}`: the name of the Markdown file
|
|
221
|
-
- `%{
|
|
234
|
+
- `%{filestem}`: the name of the Markdown file without the extension
|
|
235
|
+
- `%{filepath}`: the path of the Markdown file relative to the _source root_
|
|
236
|
+
- `%{filedir}`: the dirname of the `%{filepath}` (the path without the filename)
|
|
222
237
|
|
|
223
238
|
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
239
|
|
|
@@ -372,6 +387,11 @@ Likewise, if you have a nested list, make sure that nested items are indented by
|
|
|
372
387
|
|
|
373
388
|
Local images referenced in a Markdown file are automatically published to Confluence as attachments to the page.
|
|
374
389
|
|
|
390
|
+
* Relative paths (e.g. `path/to/image.png` or `../to/image.png`) resolve to absolute paths w.r.t. the Markdown document location.
|
|
391
|
+
* Absolute paths (e.g. `/path/to/image.png`) are interpreted w.r.t. to the synchronization root (typically the shell current directory).
|
|
392
|
+
|
|
393
|
+
As a security measure, resolved paths can only reference files that are in the directory hierarchy of the synchronization root; you can't use `..` to leave the top-level directory of the synchronization root.
|
|
394
|
+
|
|
375
395
|
Unfortunately, Confluence struggles with SVG images, e.g. they may only show in *edit* mode, display in a wrong size or text labels in the image may be truncated. (This seems to be a known issue in Confluence.) In order to mitigate the issue, whenever *md2conf* encounters a reference to an SVG image in a Markdown file, it checks whether a corresponding PNG image also exists in the same directory, and if a PNG image is found, it is published instead.
|
|
376
396
|
|
|
377
397
|
External images referenced with an absolute URL retain the original URL.
|
|
@@ -605,7 +625,9 @@ Specifically, image references for status labels (e.g. `![My label][STATUS-RED]`
|
|
|
605
625
|
|
|
606
626
|
### Running the tool
|
|
607
627
|
|
|
608
|
-
|
|
628
|
+
#### Command line
|
|
629
|
+
|
|
630
|
+
You can synchronize a (directory of) Markdown file(s) with Confluence using the command-line tool `md2conf`:
|
|
609
631
|
|
|
610
632
|
```sh
|
|
611
633
|
$ python3 -m md2conf sample/index.md
|
|
@@ -643,6 +665,8 @@ options:
|
|
|
643
665
|
--no-render-drawio Upload draw.io diagram sources as Confluence page attachments. (Marketplace app required to display.)
|
|
644
666
|
--render-mermaid Render Mermaid diagrams as image files. (Installed utility required to convert.)
|
|
645
667
|
--no-render-mermaid Upload Mermaid diagram sources as Confluence page attachments. (Marketplace app required to display.)
|
|
668
|
+
--render-plantuml Render PlantUML diagrams as image files. (Installed utility required to convert.)
|
|
669
|
+
--no-render-plantuml Upload PlantUML diagram sources as Confluence page attachments. (Marketplace app required to display.)
|
|
646
670
|
--render-latex Render LaTeX formulas as image files. (Matplotlib required to convert.)
|
|
647
671
|
--no-render-latex Inline LaTeX formulas in Confluence page. (Marketplace app required to display.)
|
|
648
672
|
--diagram-output-format {png,svg}
|
|
@@ -667,6 +691,58 @@ options:
|
|
|
667
691
|
Apply custom headers to all Confluence API requests.
|
|
668
692
|
```
|
|
669
693
|
|
|
694
|
+
#### Python
|
|
695
|
+
|
|
696
|
+
*md2conf* has a Python interface. Create a `ConnectionProperties` object to set connection parameters to the Confluence server, and a `DocumentOptions` object to configure how Markdown files are converted into pages on a Confluence wiki site. Open a connection to the Confluence server with the context manager `ConfluenceAPI`, and instantiate a `Publisher` to start converting documents.
|
|
697
|
+
|
|
698
|
+
```python
|
|
699
|
+
from md2conf.api import ConfluenceAPI
|
|
700
|
+
from md2conf.environment import ConnectionProperties
|
|
701
|
+
from md2conf.options import ConverterOptions, DocumentOptions, ImageLayoutOptions, LayoutOptions, TableLayoutOptions
|
|
702
|
+
from md2conf.publisher import Publisher
|
|
703
|
+
|
|
704
|
+
properties = ConnectionProperties(
|
|
705
|
+
api_url=...,
|
|
706
|
+
domain=...,
|
|
707
|
+
base_path=...,
|
|
708
|
+
user_name=...,
|
|
709
|
+
api_key=...,
|
|
710
|
+
space_key=...,
|
|
711
|
+
headers=...,
|
|
712
|
+
)
|
|
713
|
+
options = DocumentOptions(
|
|
714
|
+
root_page_id=...,
|
|
715
|
+
keep_hierarchy=...,
|
|
716
|
+
title_prefix=...,
|
|
717
|
+
generated_by=...,
|
|
718
|
+
converter=ConverterOptions(
|
|
719
|
+
heading_anchors=...,
|
|
720
|
+
ignore_invalid_url=...,
|
|
721
|
+
skip_title_heading=...,
|
|
722
|
+
prefer_raster=...,
|
|
723
|
+
render_drawio=...,
|
|
724
|
+
render_mermaid=...,
|
|
725
|
+
render_plantuml=...,
|
|
726
|
+
render_latex=...,
|
|
727
|
+
diagram_output_format=...,
|
|
728
|
+
webui_links=...,
|
|
729
|
+
use_panel=...,
|
|
730
|
+
layout=LayoutOptions(
|
|
731
|
+
image=ImageLayoutOptions(
|
|
732
|
+
alignment=...,
|
|
733
|
+
max_width=...,
|
|
734
|
+
),
|
|
735
|
+
table=TableLayoutOptions(
|
|
736
|
+
width=...,
|
|
737
|
+
display_mode=...,
|
|
738
|
+
),
|
|
739
|
+
),
|
|
740
|
+
),
|
|
741
|
+
)
|
|
742
|
+
with ConfluenceAPI(properties) as api:
|
|
743
|
+
Publisher(api, options).process(mdpath)
|
|
744
|
+
```
|
|
745
|
+
|
|
670
746
|
### Confluence REST API v1 vs. v2
|
|
671
747
|
|
|
672
748
|
*md2conf* version 0.3.0 has switched to using [Confluence REST API v2](https://developer.atlassian.com/cloud/confluence/rest/v2/) for API calls such as retrieving current page content. Earlier versions used [Confluence REST API v1](https://developer.atlassian.com/cloud/confluence/rest/v1/) exclusively. Unfortunately, Atlassian has decommissioned Confluence REST API v1 for several endpoints in Confluence Cloud as of due date March 31, 2025, and we don't have access to an environment where we could test retired v1 endpoints.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
markdown_to_confluence-0.5.3.dist-info/licenses/LICENSE,sha256=SEEBf2BMI1LUHnDvyHnV6L12A6zTAOQcsyMvaawAXWo,1077
|
|
2
|
+
md2conf/__init__.py,sha256=CqQHkYFE1AAs4GhMm1nConbt3V7wksgDXzqm5mg6F6I,402
|
|
3
|
+
md2conf/__main__.py,sha256=Wxz6Rx1Sx_J0FbaCY2HpByHqTMbxDEs1v5VzfdQh32U,13891
|
|
4
|
+
md2conf/api.py,sha256=GVPElSP9qILA5IaEvtKaxjsoRQSsGMMoRhCxjjzLadM,42703
|
|
5
|
+
md2conf/attachment.py,sha256=Nc3qGDENWBnsI6OVwMLXnk0EyEITpvov9MluDFD90ZI,1689
|
|
6
|
+
md2conf/coalesce.py,sha256=YHnqFwow5wCj6OQ3oosig01D2lxWusAScMF4HAUO2-g,1305
|
|
7
|
+
md2conf/collection.py,sha256=ukN74VCa4HaGSh6tLXpLd0j_UNPywcnKI0X7usgdSCo,824
|
|
8
|
+
md2conf/compatibility.py,sha256=4ZNN6VLqxSbI1kowdsPproGZqwxBISys4Z22vBfe6Z8,687
|
|
9
|
+
md2conf/converter.py,sha256=C6cROI8bUswyNZ1jSC0lf_9J-S6ojrXLbrkSDALuKR8,63065
|
|
10
|
+
md2conf/csf.py,sha256=6H9G-5cZyyWMJr0tFskPNiWdQ2Ehq-V8EhlvvxhukWY,6582
|
|
11
|
+
md2conf/emoticon.py,sha256=0g4rkx3d58xU4nnLak5ms7i0FSDnq0WJrLVFRgGyLC8,542
|
|
12
|
+
md2conf/entities.dtd,sha256=M6NzqL5N7dPs_eUA_6sDsiSLzDaAacrx9LdttiufvYU,30215
|
|
13
|
+
md2conf/environment.py,sha256=TfNEz3Pyw9qe7f8i7e_kph16c09fhZ4cLNZZzIjmI18,3892
|
|
14
|
+
md2conf/extension.py,sha256=5d2CtfULwiqlqwpOqbvlVu2KQ-kVBkDDYAhnHXbq79I,2227
|
|
15
|
+
md2conf/external.py,sha256=ZRL0mCY02Gp4XfqRdbap2YdOihSpY2LA6tdGJBfC-FQ,1693
|
|
16
|
+
md2conf/formatting.py,sha256=ygL59VgpioX069axEX-7XjKs0sUjTfIZiBE5fWmITxc,4557
|
|
17
|
+
md2conf/frontmatter.py,sha256=iWtn_oXoLQxvCsdI3OXs1ylWGmB-gc7mMLpSGg113i4,1888
|
|
18
|
+
md2conf/image.py,sha256=udlnUY_xOmI-PJAqWqXBF7hAO3pj_VW7u5hXhBw4HcA,5260
|
|
19
|
+
md2conf/latex.py,sha256=haZKkUxSEcPj3fVmiIVZAwgszqNqGLk1GQ7i8KGHpo0,2226
|
|
20
|
+
md2conf/local.py,sha256=eY3WpY-lNzLZeAfxX1ACVEhuzz0HDYX_sNQogJfkqcM,3673
|
|
21
|
+
md2conf/markdown.py,sha256=4Km-AbQH04nDgPF0ijo-Ld7o8jTPXzENIMn7P1qIk0o,3148
|
|
22
|
+
md2conf/matcher.py,sha256=Xg4YSb87iPkCzhKuKytBut6NOkEab3IM-AjzXbwy64U,6774
|
|
23
|
+
md2conf/metadata.py,sha256=NOjbCIrwLgTIIeNgmo7w5JXuT-pxOXBGSg-irfdpokk,976
|
|
24
|
+
md2conf/options.py,sha256=MJka7h0ly6D5QQNXEi9ios1uQbqHQpyn07XciHV-mF8,4385
|
|
25
|
+
md2conf/png.py,sha256=wbo8tgm8Vxi56q5PLoTutZBU6qF73KJ6TJbs7G7LPG4,6166
|
|
26
|
+
md2conf/processor.py,sha256=yqRb1Njn8XWxx22byq5nrobeH9AOp55mHTVoBUJlN2A,10203
|
|
27
|
+
md2conf/publisher.py,sha256=6WfyZxWB59PrOro6wgkgXuFcnf7AqarlUfHo-Twzu9k,8509
|
|
28
|
+
md2conf/puppeteer-config.json,sha256=-dMTAN_7kNTGbDlfXzApl0KJpAWna9YKZdwMKbpOb60,159
|
|
29
|
+
md2conf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
md2conf/scanner.py,sha256=wvh5IlD2VnAwulqY6PUe33k1D8Uag0kqTs9pQ-YFq8g,3736
|
|
31
|
+
md2conf/serializer.py,sha256=W4_yLJfT3vLw0PUg88lpUEnvn64CjaX3ZaKgIrwcxfw,1786
|
|
32
|
+
md2conf/svg.py,sha256=b98hxpVL-yFojln8NunxQRJSabMdfvazo7FkFySjALY,11054
|
|
33
|
+
md2conf/text.py,sha256=cnYV_JQp_v91LbQHo3qvxcEuhIdaPjCjkmLOKINcNv4,1736
|
|
34
|
+
md2conf/toc.py,sha256=HCtnREEucmGfqzwq-Dk6Q4ZgxW5Z8nZk6lg8UfhcoI0,2413
|
|
35
|
+
md2conf/uri.py,sha256=my0deyR5SlppJrYCbXF1Zz94QA1JT-HTWe9pKw7AJ_A,1158
|
|
36
|
+
md2conf/xml.py,sha256=uaaUDs0hfluNX74dfkY_Dxu1KmeNDGogpGRGpUVEfE4,5526
|
|
37
|
+
md2conf/drawio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
md2conf/drawio/extension.py,sha256=-k0pAhIZXDjiKeHlTJl2MGWMYpG-PmXK91-_vBMQZnE,4389
|
|
39
|
+
md2conf/drawio/render.py,sha256=veSu5gjm5ggLnmaH7uvH9qNeOygBJpqhSKK_LJs0QTk,8581
|
|
40
|
+
md2conf/mermaid/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
+
md2conf/mermaid/config.py,sha256=5Dec2QcdB_GtnuXIW6nhJK8J5caduNZU1oz1mcmmb44,376
|
|
42
|
+
md2conf/mermaid/extension.py,sha256=1drXVM_KbS00dcjSCRru0wwbil4zq3aR81dHMhfe7zA,4021
|
|
43
|
+
md2conf/mermaid/render.py,sha256=cGq3hOemgsbh6btt7uJaRxzH4EmGis0K6_qNW5YrhIk,1834
|
|
44
|
+
md2conf/mermaid/scanner.py,sha256=oIpaNxiZBNcmggnjlyYGcIVOXcYQWjf1lEVdyIwE4xE,1379
|
|
45
|
+
md2conf/plantuml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
md2conf/plantuml/config.py,sha256=j0ONhkzmAPagh00ltamTKlVEvXa6R284We9pDxRy-5U,378
|
|
47
|
+
md2conf/plantuml/extension.py,sha256=-WEL6Ssa3Kz_7D_AQSnB1da0bylyYZivmRQwlOBfYcM,6281
|
|
48
|
+
md2conf/plantuml/render.py,sha256=fwwKJIv_q1Fm9Vd8af7OcEG-5MkojZ-fxrQgA33grE8,3818
|
|
49
|
+
md2conf/plantuml/scanner.py,sha256=Oso6VbHVuMaPMKMazQc_bf4hhOT5WeJN5WiVPM8peyM,1347
|
|
50
|
+
markdown_to_confluence-0.5.3.dist-info/METADATA,sha256=7r3TveKAaJPBk14SGcJHuCXuDYD0rt7KUK6a_A8p1gE,43775
|
|
51
|
+
markdown_to_confluence-0.5.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
52
|
+
markdown_to_confluence-0.5.3.dist-info/entry_points.txt,sha256=F1zxa1wtEObtbHS-qp46330WVFLHdMnV2wQ-ZorRmX0,50
|
|
53
|
+
markdown_to_confluence-0.5.3.dist-info/top_level.txt,sha256=_FJfl_kHrHNidyjUOuS01ngu_jDsfc-ZjSocNRJnTzU,8
|
|
54
|
+
markdown_to_confluence-0.5.3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
55
|
+
markdown_to_confluence-0.5.3.dist-info/RECORD,,
|
md2conf/__init__.py
CHANGED
|
@@ -5,9 +5,9 @@ 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.3"
|
|
9
9
|
__author__ = "Levente Hunyadi"
|
|
10
|
-
__copyright__ = "Copyright 2022-
|
|
10
|
+
__copyright__ = "Copyright 2022-2026, Levente Hunyadi"
|
|
11
11
|
__license__ = "MIT"
|
|
12
12
|
__maintainer__ = "Levente Hunyadi"
|
|
13
13
|
__status__ = "Production"
|
md2conf/__main__.py
CHANGED
|
@@ -4,7 +4,7 @@ Publish Markdown files to Confluence wiki.
|
|
|
4
4
|
Parses Markdown files, converts Markdown content into the Confluence Storage Format (XHTML), and invokes
|
|
5
5
|
Confluence API endpoints to upload images and content.
|
|
6
6
|
|
|
7
|
-
Copyright 2022-
|
|
7
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
8
8
|
|
|
9
9
|
:see: https://github.com/hunyadi/md2conf
|
|
10
10
|
"""
|
|
@@ -19,10 +19,10 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Any, Iterable, Literal, Sequence
|
|
20
20
|
|
|
21
21
|
from . import __version__
|
|
22
|
-
from .
|
|
23
|
-
from .environment import ArgumentError,
|
|
24
|
-
from .extra import override
|
|
22
|
+
from .compatibility import override
|
|
23
|
+
from .environment import ArgumentError, ConfluenceSiteProperties, ConnectionProperties
|
|
25
24
|
from .metadata import ConfluenceSiteMetadata
|
|
25
|
+
from .options import ConfluencePageID, ConverterOptions, DocumentOptions, ImageLayoutOptions, LayoutOptions
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class Arguments(argparse.Namespace):
|
|
@@ -44,6 +44,7 @@ class Arguments(argparse.Namespace):
|
|
|
44
44
|
prefer_raster: bool
|
|
45
45
|
render_drawio: bool
|
|
46
46
|
render_mermaid: bool
|
|
47
|
+
render_plantuml: bool
|
|
47
48
|
render_latex: bool
|
|
48
49
|
diagram_output_format: Literal["png", "svg"]
|
|
49
50
|
local: bool
|
|
@@ -191,6 +192,19 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
191
192
|
action="store_false",
|
|
192
193
|
help="Upload Mermaid diagram sources as Confluence page attachments. (Marketplace app required to display.)",
|
|
193
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
|
+
)
|
|
194
208
|
parser.add_argument(
|
|
195
209
|
"--render-latex",
|
|
196
210
|
dest="render_latex",
|
|
@@ -322,23 +336,30 @@ def main() -> None:
|
|
|
322
336
|
format="%(asctime)s - %(levelname)s - %(funcName)s [%(lineno)d] - %(message)s",
|
|
323
337
|
)
|
|
324
338
|
|
|
325
|
-
options =
|
|
326
|
-
heading_anchors=args.heading_anchors,
|
|
327
|
-
ignore_invalid_url=args.ignore_invalid_url,
|
|
328
|
-
skip_title_heading=args.skip_title_heading,
|
|
329
|
-
title_prefix=args.title_prefix,
|
|
330
|
-
generated_by=args.generated_by,
|
|
339
|
+
options = DocumentOptions(
|
|
331
340
|
root_page_id=ConfluencePageID(args.root_page) if args.root_page else None,
|
|
332
341
|
keep_hierarchy=args.keep_hierarchy,
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
+
title_prefix=args.title_prefix,
|
|
343
|
+
generated_by=args.generated_by,
|
|
344
|
+
converter=ConverterOptions(
|
|
345
|
+
heading_anchors=args.heading_anchors,
|
|
346
|
+
ignore_invalid_url=args.ignore_invalid_url,
|
|
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
|
+
),
|
|
342
363
|
)
|
|
343
364
|
if args.local:
|
|
344
365
|
from .local import LocalConverter
|
|
@@ -364,7 +385,7 @@ def main() -> None:
|
|
|
364
385
|
from .publisher import Publisher
|
|
365
386
|
|
|
366
387
|
try:
|
|
367
|
-
properties =
|
|
388
|
+
properties = ConnectionProperties(
|
|
368
389
|
api_url=args.api_url,
|
|
369
390
|
domain=args.domain,
|
|
370
391
|
base_path=args.path,
|
|
@@ -377,10 +398,7 @@ def main() -> None:
|
|
|
377
398
|
parser.error(str(e))
|
|
378
399
|
try:
|
|
379
400
|
with ConfluenceAPI(properties) as api:
|
|
380
|
-
Publisher(
|
|
381
|
-
api,
|
|
382
|
-
options,
|
|
383
|
-
).process(args.mdpath)
|
|
401
|
+
Publisher(api, options).process(args.mdpath)
|
|
384
402
|
except HTTPError as err:
|
|
385
403
|
logging.error(err)
|
|
386
404
|
|
md2conf/api.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Publish Markdown files to Confluence wiki.
|
|
3
3
|
|
|
4
|
-
Copyright 2022-
|
|
4
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
5
5
|
|
|
6
6
|
:see: https://github.com/hunyadi/md2conf
|
|
7
7
|
"""
|
|
@@ -25,8 +25,8 @@ import requests
|
|
|
25
25
|
import truststore
|
|
26
26
|
from requests.adapters import HTTPAdapter
|
|
27
27
|
|
|
28
|
-
from .
|
|
29
|
-
from .
|
|
28
|
+
from .compatibility import override
|
|
29
|
+
from .environment import ArgumentError, ConfluenceError, ConnectionProperties, PageError
|
|
30
30
|
from .metadata import ConfluenceSiteMetadata
|
|
31
31
|
from .serializer import JsonType, json_to_object, object_to_json_payload
|
|
32
32
|
|
|
@@ -35,6 +35,7 @@ T = TypeVar("T")
|
|
|
35
35
|
# spellchecker: disable
|
|
36
36
|
mimetypes.add_type("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx", strict=True)
|
|
37
37
|
mimetypes.add_type("text/vnd.mermaid", ".mmd", strict=True)
|
|
38
|
+
mimetypes.add_type("text/vnd.plantuml", ".puml", strict=True)
|
|
38
39
|
mimetypes.add_type("application/vnd.oasis.opendocument.presentation", ".odp", strict=True)
|
|
39
40
|
mimetypes.add_type("application/vnd.oasis.opendocument.spreadsheet", ".ods", strict=True)
|
|
40
41
|
mimetypes.add_type("application/vnd.oasis.opendocument.text", ".odt", strict=True)
|
|
@@ -370,11 +371,11 @@ class ConfluenceAPI:
|
|
|
370
371
|
Encapsulates operations that can be invoked via the [Confluence REST API](https://developer.atlassian.com/cloud/confluence/rest/v2/).
|
|
371
372
|
"""
|
|
372
373
|
|
|
373
|
-
properties:
|
|
374
|
+
properties: ConnectionProperties
|
|
374
375
|
session: "ConfluenceSession | None" = None
|
|
375
376
|
|
|
376
|
-
def __init__(self, properties:
|
|
377
|
-
self.properties = properties or
|
|
377
|
+
def __init__(self, properties: ConnectionProperties | None = None) -> None:
|
|
378
|
+
self.properties = properties or ConnectionProperties()
|
|
378
379
|
|
|
379
380
|
def __enter__(self) -> "ConfluenceSession":
|
|
380
381
|
"""
|
|
@@ -626,7 +627,16 @@ class ConfluenceSession:
|
|
|
626
627
|
|
|
627
628
|
return id
|
|
628
629
|
|
|
630
|
+
@overload
|
|
631
|
+
def get_space_id(self, *, space_id: str | None = None) -> str | None: ...
|
|
632
|
+
|
|
633
|
+
@overload
|
|
634
|
+
def get_space_id(self, *, space_key: str | None = None) -> str | None: ...
|
|
635
|
+
|
|
629
636
|
def get_space_id(self, *, space_id: str | None = None, space_key: str | None = None) -> str | None:
|
|
637
|
+
return self._get_space_id(space_id=space_id, space_key=space_key)
|
|
638
|
+
|
|
639
|
+
def _get_space_id(self, *, space_id: str | None = None, space_key: str | None = None) -> str | None:
|
|
630
640
|
"""
|
|
631
641
|
Coalesces a space ID or space key into a space ID, accounting for site default.
|
|
632
642
|
|
|
@@ -647,6 +657,15 @@ class ConfluenceSession:
|
|
|
647
657
|
# space ID and key are unset, and no default space is configured
|
|
648
658
|
return None
|
|
649
659
|
|
|
660
|
+
def get_homepage_id(self, space_id: str) -> str:
|
|
661
|
+
"""
|
|
662
|
+
Returns the page ID corresponding to the space home page.
|
|
663
|
+
"""
|
|
664
|
+
|
|
665
|
+
path = f"/spaces/{space_id}"
|
|
666
|
+
data = self._get(ConfluenceVersion.VERSION_2, path, dict[str, JsonType])
|
|
667
|
+
return typing.cast(str, data["homepageId"])
|
|
668
|
+
|
|
650
669
|
def get_attachment_by_name(self, page_id: str, filename: str) -> ConfluenceAttachment:
|
|
651
670
|
"""
|
|
652
671
|
Retrieves a Confluence page attachment by an unprefixed file name.
|
|
@@ -831,7 +850,7 @@ class ConfluenceSession:
|
|
|
831
850
|
query = {
|
|
832
851
|
"title": title,
|
|
833
852
|
}
|
|
834
|
-
space_id = self.
|
|
853
|
+
space_id = self._get_space_id(space_id=space_id, space_key=space_key)
|
|
835
854
|
if space_id is not None:
|
|
836
855
|
query["space-id"] = space_id
|
|
837
856
|
|
|
@@ -1006,7 +1025,7 @@ class ConfluenceSession:
|
|
|
1006
1025
|
:returns: Confluence page ID of a matching page (if found), or `None`.
|
|
1007
1026
|
"""
|
|
1008
1027
|
|
|
1009
|
-
space_id = self.
|
|
1028
|
+
space_id = self._get_space_id(space_id=space_id, space_key=space_key)
|
|
1010
1029
|
path = "/pages"
|
|
1011
1030
|
query = {"title": title}
|
|
1012
1031
|
if space_id is not None:
|
md2conf/attachment.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ImageData:
|
|
16
|
+
path: Path
|
|
17
|
+
description: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class EmbeddedFileData:
|
|
22
|
+
data: bytes
|
|
23
|
+
description: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AttachmentCatalog:
|
|
27
|
+
"Maintains a list of files and binary data to be uploaded to Confluence as attachments."
|
|
28
|
+
|
|
29
|
+
images: list[ImageData]
|
|
30
|
+
embedded_files: dict[str, EmbeddedFileData]
|
|
31
|
+
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
self.images = []
|
|
34
|
+
self.embedded_files = {}
|
|
35
|
+
|
|
36
|
+
def add_image(self, data: ImageData) -> None:
|
|
37
|
+
self.images.append(data)
|
|
38
|
+
|
|
39
|
+
def add_embed(self, filename: str, data: EmbeddedFileData) -> None:
|
|
40
|
+
self.embedded_files[filename] = data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def attachment_name(ref: Path | str) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Safe name for use with attachment uploads.
|
|
46
|
+
|
|
47
|
+
Mutates a relative path such that it meets Confluence's attachment naming requirements.
|
|
48
|
+
|
|
49
|
+
Allowed characters:
|
|
50
|
+
|
|
51
|
+
* Alphanumeric characters: 0-9, a-z, A-Z
|
|
52
|
+
* Special characters: hyphen (-), underscore (_), period (.)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
if isinstance(ref, Path):
|
|
56
|
+
path = ref
|
|
57
|
+
else:
|
|
58
|
+
path = Path(ref)
|
|
59
|
+
|
|
60
|
+
if path.drive or path.root:
|
|
61
|
+
raise ValueError(f"required: relative path; got: {ref}")
|
|
62
|
+
|
|
63
|
+
regexp = re.compile(r"[^\-0-9A-Za-z_.]", re.UNICODE)
|
|
64
|
+
|
|
65
|
+
def replace_part(part: str) -> str:
|
|
66
|
+
if part == "..":
|
|
67
|
+
return "PAR"
|
|
68
|
+
else:
|
|
69
|
+
return regexp.sub("_", part)
|
|
70
|
+
|
|
71
|
+
parts = [replace_part(p) for p in path.parts]
|
|
72
|
+
return Path(*parts).as_posix().replace("/", "_")
|
md2conf/coalesce.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2026, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import dataclasses
|
|
11
|
+
from typing import Any, ClassVar, Protocol, TypeVar
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataclassInstance(Protocol):
|
|
15
|
+
__dataclass_fields__: ClassVar[dict[str, dataclasses.Field[Any]]]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
D = TypeVar("D", bound=DataclassInstance)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def coalesce(target: D, source: D) -> D:
|
|
22
|
+
"""
|
|
23
|
+
Implements nullish coalescing assignment on each field of a data-class.
|
|
24
|
+
|
|
25
|
+
Iterates over each field of the data-class, and evaluates the right operand and assigns it to the left only if
|
|
26
|
+
the left operand is `None`. Applies recursively when the field is a data-class.
|
|
27
|
+
|
|
28
|
+
:returns: A newly created data-class instance.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
updates: dict[str, Any] = {}
|
|
32
|
+
for field in dataclasses.fields(target):
|
|
33
|
+
target_field = getattr(target, field.name, None)
|
|
34
|
+
source_field = getattr(source, field.name, None)
|
|
35
|
+
|
|
36
|
+
if target_field is None:
|
|
37
|
+
if source_field is not None:
|
|
38
|
+
updates[field.name] = copy.deepcopy(source_field)
|
|
39
|
+
elif dataclasses.is_dataclass(field.type):
|
|
40
|
+
if source_field is not None:
|
|
41
|
+
updates[field.name] = coalesce(target_field, source_field)
|
|
42
|
+
|
|
43
|
+
return dataclasses.replace(target, **updates)
|
md2conf/collection.py
CHANGED