markdown-to-confluence 0.1.12__py3-none-any.whl → 0.1.13__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.1.12.dist-info → markdown_to_confluence-0.1.13.dist-info}/LICENSE +21 -21
- {markdown_to_confluence-0.1.12.dist-info → markdown_to_confluence-0.1.13.dist-info}/METADATA +168 -168
- markdown_to_confluence-0.1.13.dist-info/RECORD +16 -0
- {markdown_to_confluence-0.1.12.dist-info → markdown_to_confluence-0.1.13.dist-info}/WHEEL +1 -1
- {markdown_to_confluence-0.1.12.dist-info → markdown_to_confluence-0.1.13.dist-info}/zip-safe +1 -1
- md2conf/__init__.py +13 -13
- md2conf/__main__.py +139 -140
- md2conf/api.py +459 -458
- md2conf/application.py +154 -154
- md2conf/converter.py +626 -624
- md2conf/entities.dtd +537 -537
- md2conf/processor.py +91 -91
- md2conf/properties.py +52 -52
- markdown_to_confluence-0.1.12.dist-info/RECORD +0 -16
- {markdown_to_confluence-0.1.12.dist-info → markdown_to_confluence-0.1.13.dist-info}/entry_points.txt +0 -0
- {markdown_to_confluence-0.1.12.dist-info → markdown_to_confluence-0.1.13.dist-info}/top_level.txt +0 -0
md2conf/application.py
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os.path
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Dict, Optional
|
|
5
|
-
|
|
6
|
-
from .api import ConfluenceSession
|
|
7
|
-
from .converter import (
|
|
8
|
-
ConfluenceDocument,
|
|
9
|
-
ConfluenceDocumentOptions,
|
|
10
|
-
ConfluencePageMetadata,
|
|
11
|
-
attachment_name,
|
|
12
|
-
extract_qualified_id,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
LOGGER = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Application:
|
|
19
|
-
"The entry point for Markdown to Confluence conversion."
|
|
20
|
-
|
|
21
|
-
api: ConfluenceSession
|
|
22
|
-
options: ConfluenceDocumentOptions
|
|
23
|
-
|
|
24
|
-
def __init__(
|
|
25
|
-
self, api: ConfluenceSession, options: ConfluenceDocumentOptions
|
|
26
|
-
) -> None:
|
|
27
|
-
self.api = api
|
|
28
|
-
self.options = options
|
|
29
|
-
|
|
30
|
-
def synchronize(self, path: Path) -> None:
|
|
31
|
-
"Synchronizes a single Markdown page or a directory of Markdown pages."
|
|
32
|
-
|
|
33
|
-
if path.is_dir():
|
|
34
|
-
self.synchronize_directory(path)
|
|
35
|
-
elif path.is_file():
|
|
36
|
-
self.synchronize_page(path)
|
|
37
|
-
else:
|
|
38
|
-
raise ValueError(f"expected: valid file or directory path; got: {path}")
|
|
39
|
-
|
|
40
|
-
def synchronize_page(self, page_path: Path) -> None:
|
|
41
|
-
"Synchronizes a single Markdown page with Confluence."
|
|
42
|
-
|
|
43
|
-
self._synchronize_page(page_path, {})
|
|
44
|
-
|
|
45
|
-
def synchronize_directory(self, local_dir: Path) -> None:
|
|
46
|
-
"Synchronizes a directory of Markdown pages with Confluence."
|
|
47
|
-
|
|
48
|
-
page_metadata: Dict[Path, ConfluencePageMetadata] = {}
|
|
49
|
-
LOGGER.info(f"Synchronizing directory: {local_dir}")
|
|
50
|
-
|
|
51
|
-
# Step 1: build index of all page metadata
|
|
52
|
-
# NOTE: Pathlib.walk() is implemented only in Python 3.12+
|
|
53
|
-
# so sticking for old os.walk
|
|
54
|
-
for root, directories, files in os.walk(local_dir):
|
|
55
|
-
for file_name in files:
|
|
56
|
-
# Reconstitute Path object back
|
|
57
|
-
docfile = (Path(root) / file_name).absolute()
|
|
58
|
-
|
|
59
|
-
# Skip non-markdown files
|
|
60
|
-
if docfile.suffix.lower() != ".md":
|
|
61
|
-
continue
|
|
62
|
-
metadata = self._get_or_create_page(docfile)
|
|
63
|
-
|
|
64
|
-
LOGGER.debug(f"indexed {docfile} with metadata: {metadata}")
|
|
65
|
-
page_metadata[docfile] = metadata
|
|
66
|
-
|
|
67
|
-
LOGGER.info(f"indexed {len(page_metadata)} pages")
|
|
68
|
-
|
|
69
|
-
# Step 2: Convert each page
|
|
70
|
-
for page_path in page_metadata.keys():
|
|
71
|
-
self._synchronize_page(page_path, page_metadata)
|
|
72
|
-
|
|
73
|
-
def _synchronize_page(
|
|
74
|
-
self,
|
|
75
|
-
page_path: Path,
|
|
76
|
-
page_metadata: Dict[Path, ConfluencePageMetadata],
|
|
77
|
-
) -> None:
|
|
78
|
-
base_path = page_path.parent
|
|
79
|
-
|
|
80
|
-
LOGGER.info(f"Synchronizing page: {page_path}")
|
|
81
|
-
document = ConfluenceDocument(page_path, self.options, page_metadata)
|
|
82
|
-
|
|
83
|
-
if document.id.space_key:
|
|
84
|
-
with self.api.switch_space(document.id.space_key):
|
|
85
|
-
self._update_document(document, base_path)
|
|
86
|
-
else:
|
|
87
|
-
self._update_document(document, base_path)
|
|
88
|
-
|
|
89
|
-
def _get_or_create_page(
|
|
90
|
-
self, absolute_path: Path, title: Optional[str] = None
|
|
91
|
-
) -> ConfluencePageMetadata:
|
|
92
|
-
"""
|
|
93
|
-
Creates a new Confluence page if no page is linked in the Markdown document.
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
# parse file
|
|
97
|
-
with open(absolute_path, "r") as f:
|
|
98
|
-
document = f.read()
|
|
99
|
-
|
|
100
|
-
qualified_id, document = extract_qualified_id(document)
|
|
101
|
-
if qualified_id is not None:
|
|
102
|
-
confluence_page = self.api.get_page(
|
|
103
|
-
qualified_id.page_id, space_key=qualified_id.space_key
|
|
104
|
-
)
|
|
105
|
-
else:
|
|
106
|
-
if self.options.root_page_id is None:
|
|
107
|
-
raise ValueError(
|
|
108
|
-
"expected: Confluence page ID to act as parent for Markdown files with no linked Confluence page"
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
# use file name without extension if no title is supplied
|
|
112
|
-
if title is None:
|
|
113
|
-
title = absolute_path.stem
|
|
114
|
-
|
|
115
|
-
confluence_page = self.api.get_or_create_page(
|
|
116
|
-
title, self.options.root_page_id
|
|
117
|
-
)
|
|
118
|
-
self._update_markdown(
|
|
119
|
-
absolute_path,
|
|
120
|
-
document,
|
|
121
|
-
confluence_page.id,
|
|
122
|
-
confluence_page.space_key,
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
return ConfluencePageMetadata(
|
|
126
|
-
domain=self.api.domain,
|
|
127
|
-
base_path=self.api.base_path,
|
|
128
|
-
page_id=confluence_page.id,
|
|
129
|
-
space_key=confluence_page.space_key or self.api.space_key,
|
|
130
|
-
title=confluence_page.title or "",
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
def _update_document(self, document: ConfluenceDocument, base_path: Path) -> None:
|
|
134
|
-
for image in document.images:
|
|
135
|
-
self.api.upload_attachment(
|
|
136
|
-
document.id.page_id, base_path / image, attachment_name(image), ""
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
content = document.xhtml()
|
|
140
|
-
LOGGER.debug(f"generated Confluence Storage Format document:\n{content}")
|
|
141
|
-
self.api.update_page(document.id.page_id, content)
|
|
142
|
-
|
|
143
|
-
def _update_markdown(
|
|
144
|
-
self,
|
|
145
|
-
path: Path,
|
|
146
|
-
document: str,
|
|
147
|
-
page_id: str,
|
|
148
|
-
space_key: Optional[str],
|
|
149
|
-
) -> None:
|
|
150
|
-
with open(path, "w") as file:
|
|
151
|
-
file.write(f"<!-- confluence-page-id: {page_id} -->\n")
|
|
152
|
-
if space_key:
|
|
153
|
-
file.write(f"<!-- confluence-space-key: {space_key} -->\n")
|
|
154
|
-
file.write(document)
|
|
1
|
+
import logging
|
|
2
|
+
import os.path
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
from .api import ConfluenceSession
|
|
7
|
+
from .converter import (
|
|
8
|
+
ConfluenceDocument,
|
|
9
|
+
ConfluenceDocumentOptions,
|
|
10
|
+
ConfluencePageMetadata,
|
|
11
|
+
attachment_name,
|
|
12
|
+
extract_qualified_id,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
LOGGER = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Application:
|
|
19
|
+
"The entry point for Markdown to Confluence conversion."
|
|
20
|
+
|
|
21
|
+
api: ConfluenceSession
|
|
22
|
+
options: ConfluenceDocumentOptions
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, api: ConfluenceSession, options: ConfluenceDocumentOptions
|
|
26
|
+
) -> None:
|
|
27
|
+
self.api = api
|
|
28
|
+
self.options = options
|
|
29
|
+
|
|
30
|
+
def synchronize(self, path: Path) -> None:
|
|
31
|
+
"Synchronizes a single Markdown page or a directory of Markdown pages."
|
|
32
|
+
|
|
33
|
+
if path.is_dir():
|
|
34
|
+
self.synchronize_directory(path)
|
|
35
|
+
elif path.is_file():
|
|
36
|
+
self.synchronize_page(path)
|
|
37
|
+
else:
|
|
38
|
+
raise ValueError(f"expected: valid file or directory path; got: {path}")
|
|
39
|
+
|
|
40
|
+
def synchronize_page(self, page_path: Path) -> None:
|
|
41
|
+
"Synchronizes a single Markdown page with Confluence."
|
|
42
|
+
|
|
43
|
+
self._synchronize_page(page_path, {})
|
|
44
|
+
|
|
45
|
+
def synchronize_directory(self, local_dir: Path) -> None:
|
|
46
|
+
"Synchronizes a directory of Markdown pages with Confluence."
|
|
47
|
+
|
|
48
|
+
page_metadata: Dict[Path, ConfluencePageMetadata] = {}
|
|
49
|
+
LOGGER.info(f"Synchronizing directory: {local_dir}")
|
|
50
|
+
|
|
51
|
+
# Step 1: build index of all page metadata
|
|
52
|
+
# NOTE: Pathlib.walk() is implemented only in Python 3.12+
|
|
53
|
+
# so sticking for old os.walk
|
|
54
|
+
for root, directories, files in os.walk(local_dir):
|
|
55
|
+
for file_name in files:
|
|
56
|
+
# Reconstitute Path object back
|
|
57
|
+
docfile = (Path(root) / file_name).absolute()
|
|
58
|
+
|
|
59
|
+
# Skip non-markdown files
|
|
60
|
+
if docfile.suffix.lower() != ".md":
|
|
61
|
+
continue
|
|
62
|
+
metadata = self._get_or_create_page(docfile)
|
|
63
|
+
|
|
64
|
+
LOGGER.debug(f"indexed {docfile} with metadata: {metadata}")
|
|
65
|
+
page_metadata[docfile] = metadata
|
|
66
|
+
|
|
67
|
+
LOGGER.info(f"indexed {len(page_metadata)} pages")
|
|
68
|
+
|
|
69
|
+
# Step 2: Convert each page
|
|
70
|
+
for page_path in page_metadata.keys():
|
|
71
|
+
self._synchronize_page(page_path, page_metadata)
|
|
72
|
+
|
|
73
|
+
def _synchronize_page(
|
|
74
|
+
self,
|
|
75
|
+
page_path: Path,
|
|
76
|
+
page_metadata: Dict[Path, ConfluencePageMetadata],
|
|
77
|
+
) -> None:
|
|
78
|
+
base_path = page_path.parent
|
|
79
|
+
|
|
80
|
+
LOGGER.info(f"Synchronizing page: {page_path}")
|
|
81
|
+
document = ConfluenceDocument(page_path, self.options, page_metadata)
|
|
82
|
+
|
|
83
|
+
if document.id.space_key:
|
|
84
|
+
with self.api.switch_space(document.id.space_key):
|
|
85
|
+
self._update_document(document, base_path)
|
|
86
|
+
else:
|
|
87
|
+
self._update_document(document, base_path)
|
|
88
|
+
|
|
89
|
+
def _get_or_create_page(
|
|
90
|
+
self, absolute_path: Path, title: Optional[str] = None
|
|
91
|
+
) -> ConfluencePageMetadata:
|
|
92
|
+
"""
|
|
93
|
+
Creates a new Confluence page if no page is linked in the Markdown document.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
# parse file
|
|
97
|
+
with open(absolute_path, "r", encoding="utf-8") as f:
|
|
98
|
+
document = f.read()
|
|
99
|
+
|
|
100
|
+
qualified_id, document = extract_qualified_id(document)
|
|
101
|
+
if qualified_id is not None:
|
|
102
|
+
confluence_page = self.api.get_page(
|
|
103
|
+
qualified_id.page_id, space_key=qualified_id.space_key
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
if self.options.root_page_id is None:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
"expected: Confluence page ID to act as parent for Markdown files with no linked Confluence page"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# use file name without extension if no title is supplied
|
|
112
|
+
if title is None:
|
|
113
|
+
title = absolute_path.stem
|
|
114
|
+
|
|
115
|
+
confluence_page = self.api.get_or_create_page(
|
|
116
|
+
title, self.options.root_page_id
|
|
117
|
+
)
|
|
118
|
+
self._update_markdown(
|
|
119
|
+
absolute_path,
|
|
120
|
+
document,
|
|
121
|
+
confluence_page.id,
|
|
122
|
+
confluence_page.space_key,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return ConfluencePageMetadata(
|
|
126
|
+
domain=self.api.domain,
|
|
127
|
+
base_path=self.api.base_path,
|
|
128
|
+
page_id=confluence_page.id,
|
|
129
|
+
space_key=confluence_page.space_key or self.api.space_key,
|
|
130
|
+
title=confluence_page.title or "",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _update_document(self, document: ConfluenceDocument, base_path: Path) -> None:
|
|
134
|
+
for image in document.images:
|
|
135
|
+
self.api.upload_attachment(
|
|
136
|
+
document.id.page_id, base_path / image, attachment_name(image), ""
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
content = document.xhtml()
|
|
140
|
+
LOGGER.debug(f"generated Confluence Storage Format document:\n{content}")
|
|
141
|
+
self.api.update_page(document.id.page_id, content)
|
|
142
|
+
|
|
143
|
+
def _update_markdown(
|
|
144
|
+
self,
|
|
145
|
+
path: Path,
|
|
146
|
+
document: str,
|
|
147
|
+
page_id: str,
|
|
148
|
+
space_key: Optional[str],
|
|
149
|
+
) -> None:
|
|
150
|
+
with open(path, "w", encoding="utf-8") as file:
|
|
151
|
+
file.write(f"<!-- confluence-page-id: {page_id} -->\n")
|
|
152
|
+
if space_key:
|
|
153
|
+
file.write(f"<!-- confluence-space-key: {space_key} -->\n")
|
|
154
|
+
file.write(document)
|