cds2md 0.0.0__tar.gz

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.
cds2md-0.0.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright 2026 jdknight
2
+
3
+ Redistribution and use in source and binary forms, with or without modification,
4
+ are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this
7
+ list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
17
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
cds2md-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: cds2md
3
+ Version: 0.0.0
4
+ Summary: Confluence Data Center to Markdown Utility
5
+ Author: James Knight
6
+ Author-email: git@jdknight.me
7
+ Maintainer-email: James Knight <git@jdknight.me>
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown
10
+ License-Expression: BSD-2-Clause
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
+ Classifier: Topic :: Documentation
21
+ License-File: LICENSE
22
+ Requires-Dist: beautifulsoup4
23
+ Requires-Dist: httpx
24
+ Requires-Dist: python-slugify
25
+ Project-URL: Download, https://pypi.python.org/pypi/cds2md
26
+ Project-URL: Homepage, https://github.com/jdknight/cds2md
27
+ Project-URL: Source, https://github.com/jdknight/cds2md
28
+
29
+ # CDS2MD
30
+
31
+ [![pip Version](https://badgen.net/pypi/v/cds2md?label=PyPI)](https://pypi.python.org/pypi/cds2md)
32
+ [![Build Status](https://github.com/jdknight/cds2md/actions/workflows/build.yml/badge.svg)](https://github.com/jdknight/cds2md/actions/workflows/build.yml)
33
+
34
+ ## Overview
35
+
36
+ This utility can be used to convert Confluence® Data Center pages to Markdown.
37
+ Atlassian's Confluence Data Center is [scheduled for end-of-life in 2029][eol].
38
+ This tool can be used in an attempt migrate data from Confluence Data Center
39
+ instances to another location.
40
+
41
+ ## Requirements
42
+
43
+ * [Python][python] 3.11+
44
+
45
+ ## Installation
46
+
47
+ This tool can be installed using [pip][pip] or [pipx][pipx]:
48
+
49
+ ```shell
50
+ pipx install cds2md
51
+ (or)
52
+ pip install -U cds2md
53
+ (or)
54
+ python -m pip install -U cds2md
55
+ ```
56
+
57
+ # Usage
58
+
59
+ This tool can be invoked from a command line using:
60
+
61
+ ```none
62
+ cds2md --help
63
+ (or)
64
+ python -m cds2md --help
65
+ ```
66
+
67
+ A minimal example run is as follows:
68
+
69
+ ```
70
+ export CDS2MD_CONFLUENCE_TOKEN=TOKEN
71
+ export CDS2MD_CONFLUENCE_SPACE_KEY=MYSPACE
72
+ export CDS2MD_CONFLUENCE_URL=https://wiki.example.com/
73
+ cds2md
74
+ ```
75
+
76
+ This will fetch page information from a Confluence space and store this data
77
+ into a local cache folder (`.cds2md-cache/`). Once all pages are fetched,
78
+ content will be converted to Markdown and placed in the output directory
79
+ (`output/`).
80
+
81
+ # Options
82
+
83
+ The following is a list of available options:
84
+
85
+ ```
86
+ positional arguments:
87
+ {all,connection-check,fetch,process}
88
+ action to perform (default: all)
89
+
90
+ options:
91
+ -h, --help show this help message and exit
92
+ --ca-certificate CA_CERTIFICATE
93
+ ca certificate to use when communicating with Confluence
94
+ --cache CACHE path to store fetch cached data
95
+ --confluence-space-key CONFLUENCE_SPACE_KEY
96
+ space to query for pages
97
+ --confluence-token CONFLUENCE_TOKEN
98
+ token used to query the Confluence instance
99
+ --confluence-token-file CONFLUENCE_TOKEN_FILE
100
+ file which holds a Confluence token to use
101
+ --confluence-token-stdin
102
+ pass in the token via the standard input stream
103
+ --confluence-url CONFLUENCE_URL
104
+ url of the Confluence data center instance to interact
105
+ with
106
+ --debug enabling debugging logs
107
+ --development configure to operate in a development state
108
+ --header, -H HEADER http headers to include in requests
109
+ --ignore-tls ignore all TLS checks when communicating with Confluence
110
+ --flat ignore any Confluence-detected hierarchy structure
111
+ --output OUTPUT path to output markdown content into
112
+ --timeout TIMEOUT timeout for connection REST requests to Confluence
113
+ --tweak-admonition-type {default,docusaurus,github,gitlab,myst,plain}
114
+ admonition formatting to use (default: default)
115
+ --tweak-html-images force all images to be generated as html-based images
116
+ --tweak-html-tables force all tables to be generated as html-based tables
117
+ --version show program's version number and exit
118
+ --wrap WRAP hint to the number of characters to wrap to
119
+ ```
120
+
121
+ ----
122
+
123
+ The CDS2MD project is unaffiliated with Atlassian.\
124
+ Atlassian is a registered trademark of Atlassian Pty Ltd.\
125
+ Confluence is a registered trademark of Atlassian Pty Ltd.
126
+
127
+
128
+ [eol]: https://www.atlassian.com/licensing/data-center-end-of-life
129
+ [python]: https://www.python.org/
130
+
cds2md-0.0.0/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # CDS2MD
2
+
3
+ [![pip Version](https://badgen.net/pypi/v/cds2md?label=PyPI)](https://pypi.python.org/pypi/cds2md)
4
+ [![Build Status](https://github.com/jdknight/cds2md/actions/workflows/build.yml/badge.svg)](https://github.com/jdknight/cds2md/actions/workflows/build.yml)
5
+
6
+ ## Overview
7
+
8
+ This utility can be used to convert Confluence® Data Center pages to Markdown.
9
+ Atlassian's Confluence Data Center is [scheduled for end-of-life in 2029][eol].
10
+ This tool can be used in an attempt migrate data from Confluence Data Center
11
+ instances to another location.
12
+
13
+ ## Requirements
14
+
15
+ * [Python][python] 3.11+
16
+
17
+ ## Installation
18
+
19
+ This tool can be installed using [pip][pip] or [pipx][pipx]:
20
+
21
+ ```shell
22
+ pipx install cds2md
23
+ (or)
24
+ pip install -U cds2md
25
+ (or)
26
+ python -m pip install -U cds2md
27
+ ```
28
+
29
+ # Usage
30
+
31
+ This tool can be invoked from a command line using:
32
+
33
+ ```none
34
+ cds2md --help
35
+ (or)
36
+ python -m cds2md --help
37
+ ```
38
+
39
+ A minimal example run is as follows:
40
+
41
+ ```
42
+ export CDS2MD_CONFLUENCE_TOKEN=TOKEN
43
+ export CDS2MD_CONFLUENCE_SPACE_KEY=MYSPACE
44
+ export CDS2MD_CONFLUENCE_URL=https://wiki.example.com/
45
+ cds2md
46
+ ```
47
+
48
+ This will fetch page information from a Confluence space and store this data
49
+ into a local cache folder (`.cds2md-cache/`). Once all pages are fetched,
50
+ content will be converted to Markdown and placed in the output directory
51
+ (`output/`).
52
+
53
+ # Options
54
+
55
+ The following is a list of available options:
56
+
57
+ ```
58
+ positional arguments:
59
+ {all,connection-check,fetch,process}
60
+ action to perform (default: all)
61
+
62
+ options:
63
+ -h, --help show this help message and exit
64
+ --ca-certificate CA_CERTIFICATE
65
+ ca certificate to use when communicating with Confluence
66
+ --cache CACHE path to store fetch cached data
67
+ --confluence-space-key CONFLUENCE_SPACE_KEY
68
+ space to query for pages
69
+ --confluence-token CONFLUENCE_TOKEN
70
+ token used to query the Confluence instance
71
+ --confluence-token-file CONFLUENCE_TOKEN_FILE
72
+ file which holds a Confluence token to use
73
+ --confluence-token-stdin
74
+ pass in the token via the standard input stream
75
+ --confluence-url CONFLUENCE_URL
76
+ url of the Confluence data center instance to interact
77
+ with
78
+ --debug enabling debugging logs
79
+ --development configure to operate in a development state
80
+ --header, -H HEADER http headers to include in requests
81
+ --ignore-tls ignore all TLS checks when communicating with Confluence
82
+ --flat ignore any Confluence-detected hierarchy structure
83
+ --output OUTPUT path to output markdown content into
84
+ --timeout TIMEOUT timeout for connection REST requests to Confluence
85
+ --tweak-admonition-type {default,docusaurus,github,gitlab,myst,plain}
86
+ admonition formatting to use (default: default)
87
+ --tweak-html-images force all images to be generated as html-based images
88
+ --tweak-html-tables force all tables to be generated as html-based tables
89
+ --version show program's version number and exit
90
+ --wrap WRAP hint to the number of characters to wrap to
91
+ ```
92
+
93
+ ----
94
+
95
+ The CDS2MD project is unaffiliated with Atlassian.\
96
+ Atlassian is a registered trademark of Atlassian Pty Ltd.\
97
+ Confluence is a registered trademark of Atlassian Pty Ltd.
98
+
99
+
100
+ [eol]: https://www.atlassian.com/licensing/data-center-end-of-life
101
+ [python]: https://www.python.org/
@@ -0,0 +1,4 @@
1
+ # SPDX-License-Identifier: BSD-2-Clause
2
+ # Copyright jdknight
3
+
4
+ __version__ = '0.0.0'
@@ -0,0 +1,42 @@
1
+ # SPDX-License-Identifier: BSD-2-Clause
2
+ # Copyright jdknight
3
+
4
+ from cds2md.engine import engine
5
+ from cds2md.logger import logger
6
+ from cds2md.opts import process_options
7
+
8
+
9
+ def main() -> None:
10
+ """
11
+ publish helper mainline
12
+
13
+ The mainline for cds2md's publisher helper utility.
14
+ """
15
+
16
+ # process command line/environment options
17
+ opts = process_options()
18
+
19
+ # if running a debugging, output the configuration used
20
+ if opts.debug:
21
+ debug_opts = vars(opts).copy()
22
+
23
+ # sanitize options which may expose token/etc. information
24
+ if debug_opts.get('confluence_token'):
25
+ debug_opts['confluence_token'] = '(set)' # noqa: S105
26
+ if debug_opts.get('header'):
27
+ debug_opts['header'] = '(set)'
28
+ if debug_opts.get('headers'):
29
+ debug_opts['headers'] = '(set)'
30
+
31
+ # pretty paths
32
+ debug_opts['cache'] = debug_opts['cache'].as_posix() + '/'
33
+ debug_opts['output'] = debug_opts['output'].as_posix() + '/'
34
+
35
+ logger.debug(f'configuration: {debug_opts}')
36
+
37
+ # run the utility
38
+ engine(opts)
39
+
40
+
41
+ if __name__ == '__main__':
42
+ main() # pragma: no cover
@@ -0,0 +1,41 @@
1
+ # SPDX-License-Identifier: BSD-2-Clause
2
+ # Copyright jdknight
3
+
4
+ from argparse import Namespace
5
+ from dataclasses import dataclass
6
+ from hashlib import sha1
7
+
8
+
9
+ @dataclass
10
+ class AttachmentMetaCache:
11
+ id: int
12
+ page_id: int
13
+ size: int
14
+ title: str
15
+ type: str
16
+ url: str
17
+ version: int
18
+
19
+
20
+ @dataclass
21
+ class PageMetaCache:
22
+ id: int
23
+ version: int
24
+ parent_id: int | None = None
25
+
26
+
27
+ def cache_key(opts: Namespace) -> str:
28
+ """
29
+ generate a cache string
30
+
31
+ Build a cache string based on the configured runtime options. This will
32
+ be used to populate a cache path based on the Confluence URL and space.
33
+
34
+ Args:
35
+ opts: the runtime options
36
+
37
+ Returns:
38
+ the cache string
39
+ """
40
+ value = f'{opts.confluence_url}--{opts.confluence_space_key}'
41
+ return sha1(value.encode()).hexdigest() # noqa: S324
@@ -0,0 +1,210 @@
1
+ # SPDX-License-Identifier: BSD-2-Clause
2
+ # Copyright jdknight
3
+
4
+ from argparse import Namespace
5
+ from bs4 import BeautifulSoup
6
+ from cds2md.cache import cache_key
7
+ from cds2md.defs import CACHE_DB
8
+ from cds2md.logger import logger
9
+ from cds2md.translator import Translator
10
+ from cds2md.translator import TranslatorContext
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from slugify import slugify
14
+ import json
15
+ import shutil
16
+
17
+
18
+ @dataclass
19
+ class AttachmentCache:
20
+ id: int
21
+ name: str
22
+ version: int
23
+ target_path: Path | None = None
24
+
25
+
26
+ @dataclass
27
+ class PageCache:
28
+ attachments: list[AttachmentCache]
29
+ body: str
30
+ id: int
31
+ title: str
32
+ version: int
33
+ parent_id: int | None = None
34
+ target_path: Path | None = None
35
+
36
+
37
+ def convert(opts: Namespace) -> bool:
38
+ # ensure cache directory is prepared
39
+ cache_dir = opts.cache / cache_key(opts)
40
+ cache_db = cache_dir / CACHE_DB
41
+ if not cache_db.is_file():
42
+ logger.error('no cache database found')
43
+ return False
44
+
45
+ # extract cached fetch data to be processed
46
+ pages_cache = []
47
+ pageid2cache = {}
48
+
49
+ with cache_db.open() as f:
50
+ pages = json.load(f)['pages']
51
+
52
+ for page_id_str, page_data in pages.items():
53
+ page_atachments = page_data['attachments']
54
+ page_id = int(page_id_str)
55
+ page_parent = page_data['parent']
56
+ page_version = page_data['version']
57
+ page_cache_dir = cache_dir / page_id_str
58
+ page_cache_filename = f'{page_id}-{page_version}.json'
59
+ page_cache_file = page_cache_dir / page_cache_filename
60
+
61
+ attachments = []
62
+ for attachment_name, attachment_data in page_atachments.items():
63
+ attachment_id = attachment_data['id']
64
+ attachment_version = attachment_data['version']
65
+
66
+ attachment_cache = AttachmentCache(
67
+ id=attachment_id,
68
+ name=attachment_name,
69
+ version=attachment_version,
70
+ )
71
+ attachments.append(attachment_cache)
72
+
73
+ with page_cache_file.open() as f2:
74
+ page_cache_data = json.load(f2)
75
+ page_body = page_cache_data['body']
76
+ page_title = page_cache_data['title']
77
+
78
+ page_cache = PageCache(
79
+ attachments=attachments,
80
+ body=page_body,
81
+ id=page_id,
82
+ parent_id=page_parent,
83
+ title=page_title,
84
+ version=page_version,
85
+ )
86
+ pageid2cache[page_id] = page_cache
87
+ pages_cache.append(page_cache)
88
+
89
+ # determine the paths for each page cache to be stored
90
+ attachment2target: dict[tuple[str, str], str] = {}
91
+ title2target: dict[str, str] = {}
92
+ reserved_attachment_targets = set()
93
+
94
+ for page_cache in pages_cache:
95
+ title_slug = slugify(page_cache.title)
96
+ md_filename = f'{title_slug}.md'
97
+
98
+ # if a flat setup, place the page at the root of the output
99
+ if opts.flat:
100
+ page_path = opts.output / md_filename
101
+ # if using hierarchy hints, determine based with parent data
102
+ else:
103
+ path_parts = [
104
+ md_filename,
105
+ ]
106
+
107
+ target_parent = page_cache.parent_id
108
+ while target_parent:
109
+ parent = pageid2cache.get(target_parent)
110
+ assert parent
111
+ parent_slug = slugify(parent.title)
112
+ path_parts.append(parent_slug)
113
+ target_parent = parent.parent_id
114
+
115
+ page_path = opts.output
116
+ for s in reversed(path_parts):
117
+ page_path = page_path / s
118
+
119
+ page_cache.target_path = page_path
120
+
121
+ root_path = page_path.relative_to(opts.output)
122
+ page_full_slug = root_path.as_posix()
123
+ title2target[page_cache.title] = page_full_slug
124
+
125
+ for attachment_cache in page_cache.attachments:
126
+ attachment_pathname = Path(attachment_cache.name)
127
+ attachment_name = slugify(attachment_pathname.stem)
128
+ attachment_ext = slugify(attachment_pathname.suffix)
129
+
130
+ attachment_suffix = 1
131
+ while True:
132
+ sfx = attachment_suffix if attachment_suffix > 1 else ''
133
+ attachment_filename = f'{attachment_name}{sfx}.{attachment_ext}'
134
+ attachment_path = page_path.parent / attachment_filename
135
+
136
+ # check if we have a conflicting attachment
137
+ if attachment_path in reserved_attachment_targets:
138
+ attachment_suffix += 1
139
+ continue
140
+
141
+ break
142
+
143
+ attachment_cache.target_path = attachment_path
144
+ reserved_attachment_targets.add(attachment_path)
145
+
146
+ attachment_ref = (page_full_slug, attachment_cache.name)
147
+
148
+ attachment_root_path = attachment_path.relative_to(opts.output)
149
+ attachment_full_slug = attachment_root_path.as_posix()
150
+ attachment2target[attachment_ref] = attachment_full_slug
151
+
152
+ # process each page
153
+ ctx = TranslatorContext(
154
+ attachment2target=attachment2target,
155
+ opts=opts,
156
+ title2target=title2target,
157
+ )
158
+
159
+ for page_cache in pages_cache:
160
+ assert page_cache.target_path
161
+ doc_path = page_cache.target_path.as_posix()
162
+
163
+ # parse the page's body
164
+ logger.debug(f'processing body of page: {doc_path}')
165
+ soup = BeautifulSoup(page_cache.body, 'html.parser')
166
+
167
+ # translate the parsed content into a markdown output
168
+ translator = Translator(ctx, page_cache.title)
169
+ logger.debug(f'walking page: {doc_path}')
170
+ translator.walk(soup)
171
+
172
+ # build the final markdown document contents
173
+ final_body = ''.join(translator.output).strip() + '\n'
174
+
175
+ # write the markdown content to its output file
176
+ assert page_cache.target_path
177
+ md_file = page_cache.target_path
178
+ md_file.parent.mkdir(parents=True, exist_ok=True)
179
+ logger.debug(f'writing to markdown file: {md_file.as_posix()}')
180
+
181
+ if md_file.is_dir():
182
+ logger.error('cannot write markdown into path '
183
+ f'that is a directory: {md_file.as_posix()}')
184
+ return False
185
+
186
+ md_file.write_text(final_body)
187
+
188
+ # process each attachment
189
+ if page_cache.attachments:
190
+ logger.debug(f'processing attachments for page: {doc_path}')
191
+ page_id = page_cache.id
192
+ attachment_cache_dir = cache_dir / f'{page_id}' / 'attachments'
193
+
194
+ for attachment_cache in page_cache.attachments:
195
+ attachment_id = attachment_cache.id
196
+ attachment_version = attachment_cache.version
197
+ attachment_fname = f'{attachment_id}-{attachment_version}.dat'
198
+ attachment_data = attachment_cache_dir / attachment_fname
199
+
200
+ assert attachment_cache.target_path
201
+ attachment_folder = attachment_cache.target_path.parent
202
+ attachment_folder.mkdir(parents=True, exist_ok=True)
203
+
204
+ attachment_desc = attachment_cache.target_path.as_posix()
205
+ logger.debug(f'copying attachment (source): {attachment_data}')
206
+ logger.debug(f'copying attachment (target): {attachment_desc}')
207
+ shutil.copy2(attachment_data, attachment_cache.target_path)
208
+
209
+ return True
210
+
@@ -0,0 +1,27 @@
1
+ # SPDX-License-Identifier: BSD-2-Clause
2
+ # Copyright jdknight
3
+
4
+ from enum import StrEnum
5
+
6
+
7
+ # name of the cache database for a cached dataset
8
+ CACHE_DB = 'cache.json'
9
+
10
+ # name of the cache directory
11
+ CACHE_DIRNAME = '.cds2md-cache'
12
+
13
+
14
+ class Action(StrEnum):
15
+ ALL = 'all'
16
+ CONNECTION_CHECK = 'connection-check'
17
+ FETCH = 'fetch'
18
+ PROCESS = 'process'
19
+
20
+
21
+ class AdmonitionType(StrEnum):
22
+ DEFAULT = 'default'
23
+ DOCUSAURUS = 'docusaurus'
24
+ GITHUB = 'github'
25
+ GITLAB = 'gitlab'
26
+ MYST = 'myst'
27
+ PLAIN = 'plain'
@@ -0,0 +1,103 @@
1
+ # SPDX-License-Identifier: BSD-2-Clause
2
+ # Copyright jdknight
3
+
4
+ from argparse import Namespace
5
+ from cds2md import __version__ as cds2md_version
6
+ from cds2md.defs import Action
7
+ from cds2md.convert import convert
8
+ from cds2md.fetch import fetch
9
+ from cds2md.logger import logger
10
+ from cds2md.validate_connection import validate_connection
11
+ from cds2md.validate_space import validate_space
12
+ import httpx
13
+ import ssl
14
+
15
+
16
+ def engine(opts: Namespace) -> None:
17
+ # perform any action requiring remote work
18
+ if opts.action in [Action.ALL, Action.CONNECTION_CHECK, Action.FETCH]:
19
+ perform_web_action(opts)
20
+
21
+ # perform process action
22
+ if opts.action in [Action.ALL, Action.PROCESS]:
23
+ process(opts)
24
+
25
+
26
+ def perform_web_action(opts: Namespace) -> None:
27
+ # prepare an http client for requests
28
+ timeout = httpx.Timeout(
29
+ connect=opts.timeout,
30
+ read=15.0,
31
+ write=5.0,
32
+ pool=5.0,
33
+ )
34
+
35
+ headers: dict[str, str] = {}
36
+ headers |= opts.headers
37
+ headers |= {
38
+ # will only process json
39
+ 'Accept': 'application/json',
40
+ # configure confluence pat to perform queries with
41
+ 'Authorization': f'Bearer {opts.confluence_token}',
42
+ # user agent for our interactions
43
+ 'User-Agent': f'Cds2md/{cds2md_version}',
44
+ # ignore csrf
45
+ 'X-Atlassian-Token': 'no-check',
46
+ }
47
+
48
+ # setup ssl context
49
+ ssl_context = ssl.create_default_context()
50
+ if opts.ca_certificate:
51
+ ssl_context.load_verify_locations(cafile=opts.ca_certificate)
52
+ elif opts.ignore_tls:
53
+ ssl_context = False # type: ignore[assignment]
54
+
55
+ with httpx.Client(
56
+ headers=headers,
57
+ timeout=timeout,
58
+ verify=ssl_context,
59
+ ) as client:
60
+
61
+ # perform connection check action
62
+ if opts.action in [Action.ALL, Action.CONNECTION_CHECK]:
63
+ connection_check(opts, client)
64
+
65
+ # perform data fetch action
66
+ if opts.action in [Action.ALL, Action.FETCH]:
67
+ fetch_attempt(opts, client)
68
+
69
+
70
+ def connection_check(opts: Namespace, client: httpx.Client) -> None:
71
+ logger.info('validating availability of confluence instance...')
72
+ logger.debug(f'instance: {opts.confluence_url}')
73
+
74
+ if not validate_connection(opts, client):
75
+ raise SystemExit(3)
76
+
77
+ logger.info('confluence instance appears to be online')
78
+
79
+
80
+ def fetch_attempt(opts: Namespace, client: httpx.Client) -> None:
81
+ logger.info('validating the confluence space can be found...')
82
+ logger.debug(f'space-key: {opts.confluence_space_key}')
83
+
84
+ # verify the space exists
85
+ if not validate_space(opts, client):
86
+ raise SystemExit(4)
87
+
88
+ logger.info('confluence space has been detected')
89
+
90
+ # try to pull space content
91
+ logger.info('attempting to fetch content...')
92
+
93
+ if not fetch(opts, client):
94
+ raise SystemExit(5)
95
+
96
+
97
+ def process(opts: Namespace) -> None:
98
+ logger.info('converting cached data into markdown files')
99
+
100
+ if not convert(opts):
101
+ raise SystemExit(3)
102
+
103
+ logger.info('conversion completed')