markdown-to-confluence 0.3.5__py3-none-any.whl → 0.4.0__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.3.5.dist-info → markdown_to_confluence-0.4.0.dist-info}/METADATA +118 -7
- markdown_to_confluence-0.4.0.dist-info/RECORD +25 -0
- md2conf/__init__.py +1 -1
- md2conf/__main__.py +18 -7
- md2conf/api.py +372 -186
- md2conf/application.py +82 -70
- md2conf/collection.py +31 -0
- md2conf/converter.py +17 -10
- md2conf/emoji.py +28 -3
- md2conf/extra.py +14 -0
- md2conf/local.py +30 -35
- md2conf/metadata.py +0 -2
- md2conf/processor.py +134 -38
- md2conf/properties.py +24 -5
- md2conf/scanner.py +53 -21
- markdown_to_confluence-0.3.5.dist-info/RECORD +0 -23
- {markdown_to_confluence-0.3.5.dist-info → markdown_to_confluence-0.4.0.dist-info}/WHEEL +0 -0
- {markdown_to_confluence-0.3.5.dist-info → markdown_to_confluence-0.4.0.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.3.5.dist-info → markdown_to_confluence-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {markdown_to_confluence-0.3.5.dist-info → markdown_to_confluence-0.4.0.dist-info}/top_level.txt +0 -0
- {markdown_to_confluence-0.3.5.dist-info → markdown_to_confluence-0.4.0.dist-info}/zip-safe +0 -0
md2conf/application.py
CHANGED
|
@@ -6,22 +6,21 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
6
6
|
:see: https://github.com/hunyadi/md2conf
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import hashlib
|
|
10
9
|
import logging
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
from typing import Optional
|
|
13
12
|
|
|
14
|
-
from .api import
|
|
13
|
+
from .api import ConfluenceLabel, ConfluenceSession
|
|
15
14
|
from .converter import (
|
|
16
15
|
ConfluenceDocument,
|
|
17
16
|
ConfluenceDocumentOptions,
|
|
18
17
|
ConfluencePageID,
|
|
19
18
|
attachment_name,
|
|
20
19
|
)
|
|
20
|
+
from .extra import override
|
|
21
21
|
from .metadata import ConfluencePageMetadata
|
|
22
|
-
from .processor import Converter, Processor, ProcessorFactory
|
|
22
|
+
from .processor import Converter, DocumentNode, Processor, ProcessorFactory
|
|
23
23
|
from .properties import PageError
|
|
24
|
-
from .scanner import Scanner
|
|
25
24
|
|
|
26
25
|
LOGGER = logging.getLogger(__name__)
|
|
27
26
|
|
|
@@ -47,69 +46,76 @@ class SynchronizingProcessor(Processor):
|
|
|
47
46
|
super().__init__(options, api.site, root_dir)
|
|
48
47
|
self.api = api
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Creates a new Confluence page if no page is linked in the Markdown document.
|
|
49
|
+
@override
|
|
50
|
+
def _synchronize_tree(
|
|
51
|
+
self, root: DocumentNode, root_id: Optional[ConfluencePageID]
|
|
52
|
+
) -> None:
|
|
55
53
|
"""
|
|
54
|
+
Creates the cross-reference index and synchronizes the directory tree structure with the Confluence page hierarchy.
|
|
55
|
+
|
|
56
|
+
Creates new Confluence pages as necessary, e.g. if no page is linked in the Markdown document, or no page is found with lookup by page title.
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
Updates the original Markdown document to add tags to associate the document with its corresponding Confluence page.
|
|
59
|
+
"""
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
if root.page_id is None and root_id is None:
|
|
62
|
+
raise PageError(
|
|
63
|
+
f"expected: root page ID in options, or explicit page ID in {root.absolute_path}"
|
|
64
|
+
)
|
|
65
|
+
elif root.page_id is not None and root_id is not None:
|
|
66
|
+
if root.page_id != root_id.page_id:
|
|
64
67
|
raise PageError(
|
|
65
|
-
f"
|
|
68
|
+
f"mismatched inferred page ID of {root_id.page_id} and explicit page ID in {root.absolute_path}"
|
|
66
69
|
)
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
relative_path = absolute_path.relative_to(self.root_dir)
|
|
74
|
-
hash = hashlib.md5(relative_path.as_posix().encode("utf-8"))
|
|
75
|
-
digest = "".join(f"{c:x}" for c in hash.digest())
|
|
76
|
-
title = f"{absolute_path.stem} [{digest}]"
|
|
77
|
-
|
|
78
|
-
confluence_page = self._create_page(
|
|
79
|
-
absolute_path, document.text, title, parent_id
|
|
80
|
-
)
|
|
71
|
+
real_id = root_id
|
|
72
|
+
elif root_id is not None:
|
|
73
|
+
real_id = root_id
|
|
74
|
+
elif root.page_id is not None:
|
|
75
|
+
real_id = ConfluencePageID(root.page_id)
|
|
81
76
|
else:
|
|
82
|
-
|
|
83
|
-
confluence_page = self.api.get_page(document.page_id)
|
|
84
|
-
|
|
85
|
-
return ConfluencePageMetadata(
|
|
86
|
-
page_id=confluence_page.id,
|
|
87
|
-
space_key=self.api.space_id_to_key(confluence_page.space_id),
|
|
88
|
-
title=confluence_page.title,
|
|
89
|
-
overwrite=overwrite,
|
|
90
|
-
)
|
|
77
|
+
raise NotImplementedError("condition not exhaustive")
|
|
91
78
|
|
|
92
|
-
|
|
93
|
-
self,
|
|
94
|
-
absolute_path: Path,
|
|
95
|
-
document: str,
|
|
96
|
-
title: str,
|
|
97
|
-
parent_id: ConfluencePageID,
|
|
98
|
-
) -> ConfluencePage:
|
|
99
|
-
"""
|
|
100
|
-
Creates a new Confluence page when Markdown file doesn't have an embedded page ID yet.
|
|
101
|
-
"""
|
|
79
|
+
self._synchronize_subtree(root, real_id)
|
|
102
80
|
|
|
103
|
-
|
|
104
|
-
self
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
self.api.
|
|
81
|
+
def _synchronize_subtree(
|
|
82
|
+
self, node: DocumentNode, parent_id: ConfluencePageID
|
|
83
|
+
) -> None:
|
|
84
|
+
if node.page_id is not None:
|
|
85
|
+
# verify if page exists
|
|
86
|
+
page = self.api.get_page_properties(node.page_id)
|
|
87
|
+
update = False
|
|
88
|
+
elif node.title is not None:
|
|
89
|
+
# look up page by title
|
|
90
|
+
page = self.api.get_or_create_page(node.title, parent_id.page_id)
|
|
91
|
+
update = True
|
|
92
|
+
else:
|
|
93
|
+
# always create a new page
|
|
94
|
+
digest = self._generate_hash(node.absolute_path)
|
|
95
|
+
title = f"{node.absolute_path.stem} [{digest}]"
|
|
96
|
+
page = self.api.create_page(parent_id.page_id, title, "")
|
|
97
|
+
update = True
|
|
98
|
+
|
|
99
|
+
space_key = self.api.space_id_to_key(page.spaceId)
|
|
100
|
+
if update:
|
|
101
|
+
self._update_markdown(
|
|
102
|
+
node.absolute_path,
|
|
103
|
+
page_id=page.id,
|
|
104
|
+
space_key=space_key,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
data = ConfluencePageMetadata(
|
|
108
|
+
page_id=page.id,
|
|
109
|
+
space_key=space_key,
|
|
110
|
+
title=page.title,
|
|
109
111
|
)
|
|
110
|
-
|
|
112
|
+
self.page_metadata.add(node.absolute_path, data)
|
|
113
|
+
|
|
114
|
+
for child_node in node.children():
|
|
115
|
+
self._synchronize_subtree(child_node, ConfluencePageID(page.id))
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
@override
|
|
118
|
+
def _update_page(
|
|
113
119
|
self, page_id: ConfluencePageID, document: ConfluenceDocument, path: Path
|
|
114
120
|
) -> None:
|
|
115
121
|
"""
|
|
@@ -138,10 +144,12 @@ class SynchronizingProcessor(Processor):
|
|
|
138
144
|
|
|
139
145
|
title = None
|
|
140
146
|
if document.title is not None:
|
|
141
|
-
meta = self.page_metadata
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
meta = self.page_metadata.get(path)
|
|
148
|
+
if (
|
|
149
|
+
meta is not None
|
|
150
|
+
and meta.space_key is not None
|
|
151
|
+
and meta.title != document.title
|
|
152
|
+
):
|
|
145
153
|
conflicting_page_id = self.api.page_exists(
|
|
146
154
|
document.title, space_id=self.api.space_key_to_id(meta.space_key)
|
|
147
155
|
)
|
|
@@ -156,17 +164,23 @@ class SynchronizingProcessor(Processor):
|
|
|
156
164
|
|
|
157
165
|
self.api.update_page(page_id.page_id, content, title=title)
|
|
158
166
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
if document.labels is not None:
|
|
168
|
+
self.api.update_labels(
|
|
169
|
+
page_id.page_id,
|
|
170
|
+
[
|
|
171
|
+
ConfluenceLabel(name=label, prefix="global")
|
|
172
|
+
for label in document.labels
|
|
173
|
+
],
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def _update_markdown(self, path: Path, *, page_id: str, space_key: str) -> None:
|
|
166
177
|
"""
|
|
167
178
|
Writes the Confluence page ID and space key at the beginning of the Markdown file.
|
|
168
179
|
"""
|
|
169
180
|
|
|
181
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
182
|
+
document = file.read()
|
|
183
|
+
|
|
170
184
|
content: list[str] = []
|
|
171
185
|
|
|
172
186
|
# check if the file has frontmatter
|
|
@@ -178,9 +192,7 @@ class SynchronizingProcessor(Processor):
|
|
|
178
192
|
content.append(document[:index])
|
|
179
193
|
|
|
180
194
|
content.append(f"<!-- confluence-page-id: {page_id} -->")
|
|
181
|
-
|
|
182
|
-
content.append(f"<!-- confluence-space-key: {space_key} -->")
|
|
183
|
-
|
|
195
|
+
content.append(f"<!-- confluence-space-key: {space_key} -->")
|
|
184
196
|
content.append(document[index:])
|
|
185
197
|
|
|
186
198
|
with open(path, "w", encoding="utf-8") as file:
|
md2conf/collection.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Iterable, Optional
|
|
11
|
+
|
|
12
|
+
from .metadata import ConfluencePageMetadata
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConfluencePageCollection:
|
|
16
|
+
_metadata: dict[Path, ConfluencePageMetadata]
|
|
17
|
+
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
self._metadata = {}
|
|
20
|
+
|
|
21
|
+
def __len__(self) -> int:
|
|
22
|
+
return len(self._metadata)
|
|
23
|
+
|
|
24
|
+
def add(self, path: Path, data: ConfluencePageMetadata) -> None:
|
|
25
|
+
self._metadata[path] = data
|
|
26
|
+
|
|
27
|
+
def get(self, path: Path) -> Optional[ConfluencePageMetadata]:
|
|
28
|
+
return self._metadata.get(path)
|
|
29
|
+
|
|
30
|
+
def items(self) -> Iterable[tuple[Path, ConfluencePageMetadata]]:
|
|
31
|
+
return self._metadata.items()
|
md2conf/converter.py
CHANGED
|
@@ -24,8 +24,9 @@ import lxml.etree as ET
|
|
|
24
24
|
import markdown
|
|
25
25
|
from lxml.builder import ElementMaker
|
|
26
26
|
|
|
27
|
+
from .collection import ConfluencePageCollection
|
|
27
28
|
from .mermaid import render_diagram
|
|
28
|
-
from .metadata import
|
|
29
|
+
from .metadata import ConfluenceSiteMetadata
|
|
29
30
|
from .properties import PageError
|
|
30
31
|
from .scanner import ScannedDocument, Scanner
|
|
31
32
|
|
|
@@ -91,8 +92,10 @@ def emoji_generator(
|
|
|
91
92
|
md: markdown.Markdown,
|
|
92
93
|
) -> xml.etree.ElementTree.Element:
|
|
93
94
|
name = (alias or shortname).strip(":")
|
|
94
|
-
span = xml.etree.ElementTree.Element("span", {"data-emoji": name})
|
|
95
|
+
span = xml.etree.ElementTree.Element("span", {"data-emoji-shortname": name})
|
|
95
96
|
if uc is not None:
|
|
97
|
+
span.attrib["data-emoji-unicode"] = uc
|
|
98
|
+
|
|
96
99
|
# convert series of Unicode code point hexadecimal values into characters
|
|
97
100
|
span.text = "".join(chr(int(item, base=16)) for item in uc.split("-"))
|
|
98
101
|
else:
|
|
@@ -362,7 +365,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
362
365
|
images: list[Path]
|
|
363
366
|
embedded_images: dict[str, bytes]
|
|
364
367
|
site_metadata: ConfluenceSiteMetadata
|
|
365
|
-
page_metadata:
|
|
368
|
+
page_metadata: ConfluencePageCollection
|
|
366
369
|
|
|
367
370
|
def __init__(
|
|
368
371
|
self,
|
|
@@ -370,7 +373,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
370
373
|
path: Path,
|
|
371
374
|
root_dir: Path,
|
|
372
375
|
site_metadata: ConfluenceSiteMetadata,
|
|
373
|
-
page_metadata:
|
|
376
|
+
page_metadata: ConfluencePageCollection,
|
|
374
377
|
) -> None:
|
|
375
378
|
super().__init__()
|
|
376
379
|
self.options = options
|
|
@@ -834,7 +837,8 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
834
837
|
)
|
|
835
838
|
|
|
836
839
|
def _transform_emoji(self, elem: ET._Element) -> ET._Element:
|
|
837
|
-
shortname = elem.attrib.get("data-emoji", "")
|
|
840
|
+
shortname = elem.attrib.get("data-emoji-shortname", "")
|
|
841
|
+
unicode = elem.attrib.get("data-emoji-unicode", None)
|
|
838
842
|
alt = elem.text or ""
|
|
839
843
|
|
|
840
844
|
# <ac:emoticon ac:name="wink" ac:emoji-shortname=":wink:" ac:emoji-id="1f609" ac:emoji-fallback="😉"/>
|
|
@@ -844,8 +848,9 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
844
848
|
"emoticon",
|
|
845
849
|
{
|
|
846
850
|
# use "blue-star" as a placeholder name to ensure wiki page loads in timely manner
|
|
847
|
-
ET.QName(namespaces["ac"], "name"):
|
|
851
|
+
ET.QName(namespaces["ac"], "name"): shortname,
|
|
848
852
|
ET.QName(namespaces["ac"], "emoji-shortname"): f":{shortname}:",
|
|
853
|
+
ET.QName(namespaces["ac"], "emoji-id"): unicode,
|
|
849
854
|
ET.QName(namespaces["ac"], "emoji-fallback"): alt,
|
|
850
855
|
},
|
|
851
856
|
)
|
|
@@ -943,7 +948,7 @@ class ConfluenceStorageFormatConverter(NodeVisitor):
|
|
|
943
948
|
elif child.tag == "pre" and len(child) == 1 and child[0].tag == "code":
|
|
944
949
|
return self._transform_block(child[0])
|
|
945
950
|
|
|
946
|
-
elif child.tag == "span" and child.attrib.has_key("data-emoji"):
|
|
951
|
+
elif child.tag == "span" and child.attrib.has_key("data-emoji-shortname"):
|
|
947
952
|
return self._transform_emoji(child)
|
|
948
953
|
|
|
949
954
|
return None
|
|
@@ -1006,6 +1011,7 @@ class ConversionError(RuntimeError):
|
|
|
1006
1011
|
|
|
1007
1012
|
class ConfluenceDocument:
|
|
1008
1013
|
title: Optional[str]
|
|
1014
|
+
labels: Optional[list[str]]
|
|
1009
1015
|
links: list[str]
|
|
1010
1016
|
images: list[Path]
|
|
1011
1017
|
|
|
@@ -1019,7 +1025,7 @@ class ConfluenceDocument:
|
|
|
1019
1025
|
options: ConfluenceDocumentOptions,
|
|
1020
1026
|
root_dir: Path,
|
|
1021
1027
|
site_metadata: ConfluenceSiteMetadata,
|
|
1022
|
-
page_metadata:
|
|
1028
|
+
page_metadata: ConfluencePageCollection,
|
|
1023
1029
|
) -> tuple[ConfluencePageID, "ConfluenceDocument"]:
|
|
1024
1030
|
path = path.resolve(True)
|
|
1025
1031
|
|
|
@@ -1046,7 +1052,7 @@ class ConfluenceDocument:
|
|
|
1046
1052
|
options: ConfluenceDocumentOptions,
|
|
1047
1053
|
root_dir: Path,
|
|
1048
1054
|
site_metadata: ConfluenceSiteMetadata,
|
|
1049
|
-
page_metadata:
|
|
1055
|
+
page_metadata: ConfluencePageCollection,
|
|
1050
1056
|
) -> None:
|
|
1051
1057
|
self.options = options
|
|
1052
1058
|
|
|
@@ -1095,6 +1101,7 @@ class ConfluenceDocument:
|
|
|
1095
1101
|
self.embedded_images = converter.embedded_images
|
|
1096
1102
|
|
|
1097
1103
|
self.title = document.title or converter.toc.get_title()
|
|
1104
|
+
self.labels = document.tags
|
|
1098
1105
|
|
|
1099
1106
|
def xhtml(self) -> str:
|
|
1100
1107
|
return elements_to_string(self.root)
|
|
@@ -1146,7 +1153,7 @@ def _content_to_string(dtd_path: Path, content: str) -> str:
|
|
|
1146
1153
|
|
|
1147
1154
|
data = [
|
|
1148
1155
|
'<?xml version="1.0"?>',
|
|
1149
|
-
f'<!DOCTYPE ac:confluence PUBLIC "-//Atlassian//Confluence 4 Page//EN" "{dtd_path}">'
|
|
1156
|
+
f'<!DOCTYPE ac:confluence PUBLIC "-//Atlassian//Confluence 4 Page//EN" "{dtd_path.as_posix()}">'
|
|
1150
1157
|
f"<root{ns_attr_list}>",
|
|
1151
1158
|
]
|
|
1152
1159
|
data.append(content)
|
md2conf/emoji.py
CHANGED
|
@@ -10,7 +10,24 @@ import pathlib
|
|
|
10
10
|
|
|
11
11
|
import pymdownx.emoji1_db as emoji_db
|
|
12
12
|
|
|
13
|
-
EMOJI_PAGE_ID = "
|
|
13
|
+
EMOJI_PAGE_ID = "13500452"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def to_html(cp: int) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Returns the safe HTML representation for a Unicode code point.
|
|
19
|
+
|
|
20
|
+
Converts non-ASCII and non-printable characters into HTML entities with decimal notation.
|
|
21
|
+
|
|
22
|
+
:param cp: Unicode code point.
|
|
23
|
+
:returns: An HTML representation of the Unicode character.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
ch = chr(cp)
|
|
27
|
+
if ch.isascii() and ch.isalnum():
|
|
28
|
+
return ch
|
|
29
|
+
else:
|
|
30
|
+
return f"&#{cp};"
|
|
14
31
|
|
|
15
32
|
|
|
16
33
|
def generate_source(path: pathlib.Path) -> None:
|
|
@@ -47,11 +64,19 @@ def generate_target(path: pathlib.Path) -> None:
|
|
|
47
64
|
print("<thead><tr><th>Icon</th><th>Emoji code</th></tr></thead>", file=f)
|
|
48
65
|
print("<tbody>", file=f)
|
|
49
66
|
for key, data in emojis.items():
|
|
67
|
+
unicode = data["unicode"]
|
|
50
68
|
key = key.strip(":")
|
|
51
|
-
|
|
69
|
+
html = "".join(to_html(int(item, base=16)) for item in unicode.split("-"))
|
|
52
70
|
|
|
53
71
|
print(
|
|
54
|
-
f
|
|
72
|
+
f"<tr>\n"
|
|
73
|
+
f" <td>\n"
|
|
74
|
+
f' <ac:emoticon ac:name="{key}" ac:emoji-shortname=":{key}:" ac:emoji-id="{unicode}" ac:emoji-fallback="{html}"/>\n'
|
|
75
|
+
f" </td>\n"
|
|
76
|
+
f" <td>\n"
|
|
77
|
+
f" <code>:{key}:</code>\n"
|
|
78
|
+
f" </td>\n"
|
|
79
|
+
f"</tr>",
|
|
55
80
|
file=f,
|
|
56
81
|
)
|
|
57
82
|
print("</tbody>", file=f)
|
md2conf/extra.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish Markdown files to Confluence wiki.
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/md2conf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
if sys.version_info >= (3, 12):
|
|
12
|
+
from typing import override as override # noqa: F401
|
|
13
|
+
else:
|
|
14
|
+
from typing_extensions import override as override # noqa: F401
|
md2conf/local.py
CHANGED
|
@@ -6,17 +6,15 @@ Copyright 2022-2025, Levente Hunyadi
|
|
|
6
6
|
:see: https://github.com/hunyadi/md2conf
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import hashlib
|
|
10
9
|
import logging
|
|
11
10
|
import os
|
|
12
11
|
from pathlib import Path
|
|
13
12
|
from typing import Optional
|
|
14
13
|
|
|
15
14
|
from .converter import ConfluenceDocument, ConfluenceDocumentOptions, ConfluencePageID
|
|
15
|
+
from .extra import override
|
|
16
16
|
from .metadata import ConfluencePageMetadata, ConfluenceSiteMetadata
|
|
17
|
-
from .processor import Converter, Processor, ProcessorFactory
|
|
18
|
-
from .properties import PageError
|
|
19
|
-
from .scanner import Scanner
|
|
17
|
+
from .processor import Converter, DocumentNode, Processor, ProcessorFactory
|
|
20
18
|
|
|
21
19
|
LOGGER = logging.getLogger(__name__)
|
|
22
20
|
|
|
@@ -46,44 +44,41 @@ class LocalProcessor(Processor):
|
|
|
46
44
|
super().__init__(options, site, root_dir)
|
|
47
45
|
self.out_dir = out_dir or root_dir
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
@override
|
|
48
|
+
def _synchronize_tree(
|
|
49
|
+
self, root: DocumentNode, root_id: Optional[ConfluencePageID]
|
|
50
|
+
) -> None:
|
|
52
51
|
"""
|
|
53
|
-
|
|
52
|
+
Creates the cross-reference index.
|
|
53
|
+
|
|
54
|
+
Does not change Markdown files.
|
|
54
55
|
"""
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
raise PageError(
|
|
64
|
-
f"expected: parent page ID for Markdown file with no linked Confluence page: {absolute_path}"
|
|
57
|
+
for node in root.all():
|
|
58
|
+
if node.page_id is not None:
|
|
59
|
+
page_id = node.page_id
|
|
60
|
+
else:
|
|
61
|
+
digest = self._generate_hash(node.absolute_path)
|
|
62
|
+
LOGGER.info(
|
|
63
|
+
"Identifier %s assigned to page: %s", digest, node.absolute_path
|
|
65
64
|
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _save_document(
|
|
65
|
+
page_id = digest
|
|
66
|
+
|
|
67
|
+
self.page_metadata.add(
|
|
68
|
+
node.absolute_path,
|
|
69
|
+
ConfluencePageMetadata(
|
|
70
|
+
page_id=page_id,
|
|
71
|
+
space_key=node.space_key or self.site.space_key or "HOME",
|
|
72
|
+
title=node.title or "",
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@override
|
|
77
|
+
def _update_page(
|
|
81
78
|
self, page_id: ConfluencePageID, document: ConfluenceDocument, path: Path
|
|
82
79
|
) -> None:
|
|
83
80
|
"""
|
|
84
|
-
Saves
|
|
85
|
-
|
|
86
|
-
A derived class may invoke Confluence REST API to persist the new version.
|
|
81
|
+
Saves the document as Confluence Storage Format XHTML to the local disk.
|
|
87
82
|
"""
|
|
88
83
|
|
|
89
84
|
content = document.xhtml()
|
md2conf/metadata.py
CHANGED
|
@@ -33,10 +33,8 @@ class ConfluencePageMetadata:
|
|
|
33
33
|
:param page_id: Confluence page ID.
|
|
34
34
|
:param space_key: Confluence space key.
|
|
35
35
|
:param title: Document title.
|
|
36
|
-
:param overwrite: True if operations are allowed to update document properties (e.g. title).
|
|
37
36
|
"""
|
|
38
37
|
|
|
39
38
|
page_id: str
|
|
40
39
|
space_key: str
|
|
41
40
|
title: str
|
|
42
|
-
overwrite: bool
|