markdown-to-confluence 0.2.6__py3-none-any.whl → 0.2.7__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.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/METADATA +2 -2
- {markdown_to_confluence-0.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/RECORD +11 -11
- md2conf/__init__.py +1 -1
- md2conf/api.py +13 -2
- md2conf/application.py +4 -13
- md2conf/converter.py +73 -24
- {markdown_to_confluence-0.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/LICENSE +0 -0
- {markdown_to_confluence-0.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/WHEEL +0 -0
- {markdown_to_confluence-0.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/top_level.txt +0 -0
- {markdown_to_confluence-0.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/zip-safe +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: markdown-to-confluence
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Publish Markdown files to Confluence wiki
|
|
5
5
|
Home-page: https://github.com/hunyadi/md2conf
|
|
6
6
|
Author: Levente Hunyadi
|
|
@@ -50,7 +50,7 @@ This Python package
|
|
|
50
50
|
* Link to [sections on the same page](#getting-started) or [external locations](http://example.com/)
|
|
51
51
|
* Ordered and unordered lists
|
|
52
52
|
* Code blocks (e.g. Python, JSON, XML)
|
|
53
|
-
*
|
|
53
|
+
* Images (uploaded as Confluence page attachments or hosted externally)
|
|
54
54
|
* Tables
|
|
55
55
|
* [Table of contents](https://docs.gitlab.com/ee/user/markdown.html#table-of-contents)
|
|
56
56
|
* [Admonitions](https://python-markdown.github.io/extensions/admonition/) and alert boxes in [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) and [GitLab](https://docs.gitlab.com/ee/development/documentation/styleguide/#alert-boxes)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
md2conf/__init__.py,sha256=
|
|
1
|
+
md2conf/__init__.py,sha256=U8zdop7-AIrfwCYzWiwKfhCEPF_1QEKPt4Zwq-38LlU,402
|
|
2
2
|
md2conf/__main__.py,sha256=6iOI28W_d71tlnCMFpZwvkBmBt5-HazlZsz69gS4Oak,6894
|
|
3
|
-
md2conf/api.py,sha256=
|
|
4
|
-
md2conf/application.py,sha256
|
|
5
|
-
md2conf/converter.py,sha256=
|
|
3
|
+
md2conf/api.py,sha256=NmAbNWTrTSi2ZDGYymy70Fw6HcgrmB-Ua4re4yLJvVc,17715
|
|
4
|
+
md2conf/application.py,sha256=-kFpMRtSpQUU1hsiW5O73gL1X9McQWpvyAAEUxEnpuU,8869
|
|
5
|
+
md2conf/converter.py,sha256=S8Kka35Y99w0J00CYi-DQwsKzlHAvBfaSCf10mb1FZk,36596
|
|
6
6
|
md2conf/emoji.py,sha256=w9oiOIxzObAE7HTo3f6aETT1_D3t3yZwr88ynU4ENm0,1924
|
|
7
7
|
md2conf/entities.dtd,sha256=M6NzqL5N7dPs_eUA_6sDsiSLzDaAacrx9LdttiufvYU,30215
|
|
8
8
|
md2conf/matcher.py,sha256=mYMltZOLypK4O-SJugLgicOwUMem67hiNLg_kPFoJkU,3583
|
|
@@ -12,10 +12,10 @@ md2conf/properties.py,sha256=iVIc0h0XtS3Y2LCywX1C9cvmVQ0WljOMt8pl2MDMVCI,1990
|
|
|
12
12
|
md2conf/puppeteer-config.json,sha256=-dMTAN_7kNTGbDlfXzApl0KJpAWna9YKZdwMKbpOb60,159
|
|
13
13
|
md2conf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
md2conf/util.py,sha256=ftf60MiW7S7rW45ipWX6efP_Sv2F2qpyIDHrGA0cBiw,743
|
|
15
|
-
markdown_to_confluence-0.2.
|
|
16
|
-
markdown_to_confluence-0.2.
|
|
17
|
-
markdown_to_confluence-0.2.
|
|
18
|
-
markdown_to_confluence-0.2.
|
|
19
|
-
markdown_to_confluence-0.2.
|
|
20
|
-
markdown_to_confluence-0.2.
|
|
21
|
-
markdown_to_confluence-0.2.
|
|
15
|
+
markdown_to_confluence-0.2.7.dist-info/LICENSE,sha256=Pv43so2bPfmKhmsrmXFyAvS7M30-1i1tzjz6-dfhyOo,1077
|
|
16
|
+
markdown_to_confluence-0.2.7.dist-info/METADATA,sha256=76K_O_5b__MnKT7FuLXgCHX6hR5dZio3mK6RWR4DyCA,13551
|
|
17
|
+
markdown_to_confluence-0.2.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
18
|
+
markdown_to_confluence-0.2.7.dist-info/entry_points.txt,sha256=F1zxa1wtEObtbHS-qp46330WVFLHdMnV2wQ-ZorRmX0,50
|
|
19
|
+
markdown_to_confluence-0.2.7.dist-info/top_level.txt,sha256=_FJfl_kHrHNidyjUOuS01ngu_jDsfc-ZjSocNRJnTzU,8
|
|
20
|
+
markdown_to_confluence-0.2.7.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
21
|
+
markdown_to_confluence-0.2.7.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.2.
|
|
8
|
+
__version__ = "0.2.7"
|
|
9
9
|
__author__ = "Levente Hunyadi"
|
|
10
10
|
__copyright__ = "Copyright 2022-2024, Levente Hunyadi"
|
|
11
11
|
__license__ = "MIT"
|
md2conf/api.py
CHANGED
|
@@ -420,12 +420,23 @@ class ConfluenceSession:
|
|
|
420
420
|
new_content: str,
|
|
421
421
|
*,
|
|
422
422
|
space_key: Optional[str] = None,
|
|
423
|
+
title: Optional[str] = None,
|
|
423
424
|
) -> None:
|
|
425
|
+
"""
|
|
426
|
+
Update a page via the Confluence API.
|
|
427
|
+
|
|
428
|
+
:param page_id: The Confluence page ID.
|
|
429
|
+
:param new_content: Confluence Storage Format XHTML.
|
|
430
|
+
:param space_key: The Confluence space key (unless the default space is to be used).
|
|
431
|
+
:param title: New title to assign to the page. Needs to be unique within a space.
|
|
432
|
+
"""
|
|
433
|
+
|
|
424
434
|
page = self.get_page(page_id, space_key=space_key)
|
|
435
|
+
new_title = title or page.title
|
|
425
436
|
|
|
426
437
|
try:
|
|
427
438
|
old_content = sanitize_confluence(page.content)
|
|
428
|
-
if old_content == new_content:
|
|
439
|
+
if page.title == new_title and old_content == new_content:
|
|
429
440
|
LOGGER.info("Up-to-date page: %s", page_id)
|
|
430
441
|
return
|
|
431
442
|
except ParseError as exc:
|
|
@@ -435,7 +446,7 @@ class ConfluenceSession:
|
|
|
435
446
|
data = {
|
|
436
447
|
"id": page_id,
|
|
437
448
|
"type": "page",
|
|
438
|
-
"title":
|
|
449
|
+
"title": new_title,
|
|
439
450
|
"space": {"key": space_key or self.space_key},
|
|
440
451
|
"body": {"storage": {"value": new_content, "representation": "storage"}},
|
|
441
452
|
"version": {"minorEdit": True, "number": page.version + 1},
|
md2conf/application.py
CHANGED
|
@@ -11,8 +11,6 @@ import os.path
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Dict, List, Optional
|
|
13
13
|
|
|
14
|
-
import yaml
|
|
15
|
-
|
|
16
14
|
from .api import ConfluencePage, ConfluenceSession
|
|
17
15
|
from .converter import (
|
|
18
16
|
ConfluenceDocument,
|
|
@@ -20,7 +18,7 @@ from .converter import (
|
|
|
20
18
|
ConfluencePageMetadata,
|
|
21
19
|
ConfluenceQualifiedID,
|
|
22
20
|
attachment_name,
|
|
23
|
-
|
|
21
|
+
extract_frontmatter_title,
|
|
24
22
|
extract_qualified_id,
|
|
25
23
|
read_qualified_id,
|
|
26
24
|
)
|
|
@@ -174,7 +172,7 @@ class Application:
|
|
|
174
172
|
document = f.read()
|
|
175
173
|
|
|
176
174
|
qualified_id, document = extract_qualified_id(document)
|
|
177
|
-
|
|
175
|
+
frontmatter_title, _ = extract_frontmatter_title(document)
|
|
178
176
|
|
|
179
177
|
if qualified_id is not None:
|
|
180
178
|
confluence_page = self.api.get_page(
|
|
@@ -187,15 +185,8 @@ class Application:
|
|
|
187
185
|
)
|
|
188
186
|
|
|
189
187
|
# assign title from frontmatter if present
|
|
190
|
-
if title is None and frontmatter is not None:
|
|
191
|
-
properties = yaml.safe_load(frontmatter)
|
|
192
|
-
if isinstance(properties, dict):
|
|
193
|
-
property_title = properties.get("title")
|
|
194
|
-
if isinstance(property_title, str):
|
|
195
|
-
title = property_title
|
|
196
|
-
|
|
197
188
|
confluence_page = self._create_page(
|
|
198
|
-
absolute_path, document, title, parent_id
|
|
189
|
+
absolute_path, document, title or frontmatter_title, parent_id
|
|
199
190
|
)
|
|
200
191
|
|
|
201
192
|
return ConfluencePageMetadata(
|
|
@@ -249,7 +240,7 @@ class Application:
|
|
|
249
240
|
|
|
250
241
|
content = document.xhtml()
|
|
251
242
|
LOGGER.debug("Generated Confluence Storage Format document:\n%s", content)
|
|
252
|
-
self.api.update_page(document.id.page_id, content)
|
|
243
|
+
self.api.update_page(document.id.page_id, content, title=document.title)
|
|
253
244
|
|
|
254
245
|
def _update_markdown(
|
|
255
246
|
self,
|
md2conf/converter.py
CHANGED
|
@@ -23,6 +23,7 @@ from urllib.parse import ParseResult, urlparse, urlunparse
|
|
|
23
23
|
|
|
24
24
|
import lxml.etree as ET
|
|
25
25
|
import markdown
|
|
26
|
+
import yaml
|
|
26
27
|
from lxml.builder import ElementMaker
|
|
27
28
|
|
|
28
29
|
from . import mermaid
|
|
@@ -350,8 +351,8 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
350
351
|
heading.text = None
|
|
351
352
|
|
|
352
353
|
def _transform_link(self, anchor: ET._Element) -> Optional[ET._Element]:
|
|
353
|
-
url = anchor.attrib
|
|
354
|
-
if is_absolute_url(url):
|
|
354
|
+
url = anchor.attrib.get("href")
|
|
355
|
+
if url is None or is_absolute_url(url):
|
|
355
356
|
return None
|
|
356
357
|
|
|
357
358
|
LOGGER.debug("Found link %s relative to %s", url, self.path)
|
|
@@ -432,39 +433,72 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
432
433
|
return None
|
|
433
434
|
|
|
434
435
|
def _transform_image(self, image: ET._Element) -> ET._Element:
|
|
435
|
-
|
|
436
|
+
src = image.attrib.get("src")
|
|
436
437
|
|
|
437
|
-
if not
|
|
438
|
+
if not src:
|
|
438
439
|
raise DocumentError("image lacks `src` attribute")
|
|
439
440
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
441
|
+
attributes: Dict[str, Any] = {
|
|
442
|
+
ET.QName(namespaces["ac"], "align"): "center",
|
|
443
|
+
ET.QName(namespaces["ac"], "layout"): "center",
|
|
444
|
+
}
|
|
445
|
+
width = image.attrib.get("width")
|
|
446
|
+
if width is not None:
|
|
447
|
+
attributes.update({ET.QName(namespaces["ac"], "width"): width})
|
|
448
|
+
height = image.attrib.get("height")
|
|
449
|
+
if height is not None:
|
|
450
|
+
attributes.update({ET.QName(namespaces["ac"], "height"): height})
|
|
451
|
+
|
|
452
|
+
caption = image.attrib.get("alt")
|
|
453
|
+
|
|
454
|
+
if is_absolute_url(src):
|
|
455
|
+
return self._transform_external_image(src, caption, attributes)
|
|
456
|
+
else:
|
|
457
|
+
return self._transform_attached_image(Path(src), caption, attributes)
|
|
458
|
+
|
|
459
|
+
def _transform_external_image(
|
|
460
|
+
self, url: str, caption: Optional[str], attributes: Dict[str, Any]
|
|
461
|
+
) -> ET._Element:
|
|
462
|
+
"Emits Confluence Storage Format XHTML for an external image."
|
|
463
|
+
|
|
464
|
+
elements: List[ET._Element] = []
|
|
465
|
+
elements.append(
|
|
466
|
+
RI(
|
|
467
|
+
"url",
|
|
468
|
+
# refers to an external image
|
|
469
|
+
{ET.QName(namespaces["ri"], "value"): url},
|
|
470
|
+
)
|
|
471
|
+
)
|
|
472
|
+
if caption is not None:
|
|
473
|
+
elements.append(AC("caption", HTML.p(caption)))
|
|
444
474
|
|
|
445
|
-
|
|
475
|
+
return AC("image", attributes, *elements)
|
|
476
|
+
|
|
477
|
+
def _transform_attached_image(
|
|
478
|
+
self, path: Path, caption: Optional[str], attributes: Dict[str, Any]
|
|
479
|
+
) -> ET._Element:
|
|
480
|
+
"Emits Confluence Storage Format XHTML for an attached image."
|
|
446
481
|
|
|
447
482
|
# prefer PNG over SVG; Confluence displays SVG in wrong size, and text labels are truncated
|
|
448
|
-
png_file =
|
|
449
|
-
if
|
|
450
|
-
|
|
483
|
+
png_file = path.with_suffix(".png")
|
|
484
|
+
if path.suffix == ".svg" and (self.base_dir / png_file).exists():
|
|
485
|
+
path = png_file
|
|
451
486
|
|
|
452
|
-
self.images.append(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
{
|
|
458
|
-
ET.QName(namespaces["ac"], "align"): "center",
|
|
459
|
-
ET.QName(namespaces["ac"], "layout"): "center",
|
|
460
|
-
},
|
|
487
|
+
self.images.append(path)
|
|
488
|
+
image_name = attachment_name(path)
|
|
489
|
+
|
|
490
|
+
elements: List[ET._Element] = []
|
|
491
|
+
elements.append(
|
|
461
492
|
RI(
|
|
462
493
|
"attachment",
|
|
463
494
|
# refers to an attachment uploaded alongside the page
|
|
464
495
|
{ET.QName(namespaces["ri"], "filename"): image_name},
|
|
465
|
-
)
|
|
466
|
-
AC("caption", HTML.p(caption)),
|
|
496
|
+
)
|
|
467
497
|
)
|
|
498
|
+
if caption is not None:
|
|
499
|
+
elements.append(AC("caption", HTML.p(caption)))
|
|
500
|
+
|
|
501
|
+
return AC("image", attributes, *elements)
|
|
468
502
|
|
|
469
503
|
def _transform_block(self, code: ET._Element) -> ET._Element:
|
|
470
504
|
language = code.attrib.get("class")
|
|
@@ -907,6 +941,20 @@ def extract_frontmatter(text: str) -> Tuple[Optional[str], str]:
|
|
|
907
941
|
return extract_value(r"(?ms)\A---$(.+?)^---$", text)
|
|
908
942
|
|
|
909
943
|
|
|
944
|
+
def extract_frontmatter_title(text: str) -> Tuple[Optional[str], str]:
|
|
945
|
+
frontmatter, text = extract_frontmatter(text)
|
|
946
|
+
|
|
947
|
+
title: Optional[str] = None
|
|
948
|
+
if frontmatter is not None:
|
|
949
|
+
properties = yaml.safe_load(frontmatter)
|
|
950
|
+
if isinstance(properties, dict):
|
|
951
|
+
property_title = properties.get("title")
|
|
952
|
+
if isinstance(property_title, str):
|
|
953
|
+
title = property_title
|
|
954
|
+
|
|
955
|
+
return title, text
|
|
956
|
+
|
|
957
|
+
|
|
910
958
|
def read_qualified_id(absolute_path: Path) -> Optional[ConfluenceQualifiedID]:
|
|
911
959
|
"Reads the Confluence page ID and space key from a Markdown document."
|
|
912
960
|
|
|
@@ -944,6 +992,7 @@ class ConfluenceDocumentOptions:
|
|
|
944
992
|
|
|
945
993
|
class ConfluenceDocument:
|
|
946
994
|
id: ConfluenceQualifiedID
|
|
995
|
+
title: Optional[str]
|
|
947
996
|
links: List[str]
|
|
948
997
|
images: List[Path]
|
|
949
998
|
|
|
@@ -982,7 +1031,7 @@ class ConfluenceDocument:
|
|
|
982
1031
|
)
|
|
983
1032
|
|
|
984
1033
|
# extract frontmatter
|
|
985
|
-
|
|
1034
|
+
self.title, text = extract_frontmatter_title(text)
|
|
986
1035
|
|
|
987
1036
|
# convert to HTML
|
|
988
1037
|
html = markdown_to_html(text)
|
|
File without changes
|
|
File without changes
|
{markdown_to_confluence-0.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{markdown_to_confluence-0.2.6.dist-info → markdown_to_confluence-0.2.7.dist-info}/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|