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.
Files changed (53) hide show
  1. {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/METADATA +80 -4
  2. markdown_to_confluence-0.5.3.dist-info/RECORD +55 -0
  3. {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/licenses/LICENSE +1 -1
  4. md2conf/__init__.py +2 -2
  5. md2conf/__main__.py +42 -24
  6. md2conf/api.py +27 -8
  7. md2conf/attachment.py +72 -0
  8. md2conf/coalesce.py +43 -0
  9. md2conf/collection.py +1 -1
  10. md2conf/{extra.py → compatibility.py} +1 -1
  11. md2conf/converter.py +232 -649
  12. md2conf/csf.py +13 -11
  13. md2conf/drawio/__init__.py +0 -0
  14. md2conf/drawio/extension.py +116 -0
  15. md2conf/{drawio.py → drawio/render.py} +1 -1
  16. md2conf/emoticon.py +3 -3
  17. md2conf/environment.py +2 -2
  18. md2conf/extension.py +78 -0
  19. md2conf/external.py +49 -0
  20. md2conf/formatting.py +135 -0
  21. md2conf/frontmatter.py +70 -0
  22. md2conf/image.py +127 -0
  23. md2conf/latex.py +4 -183
  24. md2conf/local.py +8 -8
  25. md2conf/markdown.py +1 -1
  26. md2conf/matcher.py +1 -1
  27. md2conf/mermaid/__init__.py +0 -0
  28. md2conf/mermaid/config.py +20 -0
  29. md2conf/mermaid/extension.py +109 -0
  30. md2conf/{mermaid.py → mermaid/render.py} +10 -38
  31. md2conf/mermaid/scanner.py +55 -0
  32. md2conf/metadata.py +1 -1
  33. md2conf/{domain.py → options.py} +73 -16
  34. md2conf/plantuml/__init__.py +0 -0
  35. md2conf/plantuml/config.py +20 -0
  36. md2conf/plantuml/extension.py +158 -0
  37. md2conf/plantuml/render.py +139 -0
  38. md2conf/plantuml/scanner.py +56 -0
  39. md2conf/png.py +202 -0
  40. md2conf/processor.py +32 -11
  41. md2conf/publisher.py +14 -18
  42. md2conf/scanner.py +31 -128
  43. md2conf/serializer.py +2 -2
  44. md2conf/svg.py +24 -2
  45. md2conf/text.py +1 -1
  46. md2conf/toc.py +1 -1
  47. md2conf/uri.py +1 -1
  48. md2conf/xml.py +1 -1
  49. markdown_to_confluence-0.5.2.dist-info/RECORD +0 -36
  50. {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/WHEEL +0 -0
  51. {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/entry_points.txt +0 -0
  52. {markdown_to_confluence-0.5.2.dist-info → markdown_to_confluence-0.5.3.dist-info}/top_level.txt +0 -0
  53. {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.2
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.** 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/).
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
- - `%{filepath}`: the path of the Markdown file relative to the *source root*
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
- You execute the command-line tool `md2conf` to synchronize the Markdown file with Confluence:
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,,
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022-2025 Levente Hunyadi
3
+ Copyright (c) 2022-2026 Levente Hunyadi
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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.2"
8
+ __version__ = "0.5.3"
9
9
  __author__ = "Levente Hunyadi"
10
- __copyright__ = "Copyright 2022-2025, Levente Hunyadi"
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-2025, Levente Hunyadi
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 .domain import ConfluenceDocumentOptions, ConfluencePageID
23
- from .environment import ArgumentError, ConfluenceConnectionProperties, ConfluenceSiteProperties
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 = ConfluenceDocumentOptions(
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
- prefer_raster=args.prefer_raster,
334
- render_drawio=args.render_drawio,
335
- render_mermaid=args.render_mermaid,
336
- render_latex=args.render_latex,
337
- diagram_output_format=args.diagram_output_format,
338
- webui_links=args.webui_links,
339
- alignment=args.alignment,
340
- max_image_width=args.max_image_width,
341
- use_panel=args.use_panel,
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 = ConfluenceConnectionProperties(
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-2025, Levente Hunyadi
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 .environment import ArgumentError, ConfluenceConnectionProperties, ConfluenceError, PageError
29
- from .extra import override
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: ConfluenceConnectionProperties
374
+ properties: ConnectionProperties
374
375
  session: "ConfluenceSession | None" = None
375
376
 
376
- def __init__(self, properties: ConfluenceConnectionProperties | None = None) -> None:
377
- self.properties = properties or ConfluenceConnectionProperties()
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.get_space_id(space_id=space_id, space_key=space_key)
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.get_space_id(space_id=space_id, space_key=space_key)
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
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Publish Markdown files to Confluence wiki.
3
3
 
4
- Copyright 2022-2025, Levente Hunyadi
4
+ Copyright 2022-2026, Levente Hunyadi
5
5
 
6
6
  :see: https://github.com/hunyadi/md2conf
7
7
  """
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Publish Markdown files to Confluence wiki.
3
3
 
4
- Copyright 2022-2025, Levente Hunyadi
4
+ Copyright 2022-2026, Levente Hunyadi
5
5
 
6
6
  :see: https://github.com/hunyadi/md2conf
7
7
  """