From 9864d7500bebc22d1e95990a85f9795d439f00a0 Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 20:41:26 -0500 Subject: [PATCH 1/9] adjust mainline args to use pathlib Updating the working directory and output directory arguments to be processed as Path types instead of strings. This is part of an ongoing maintenance task to the implementation to use newer capabilities of Python that are supported on the oldest interpreter version this extension supports. Signed-off-by: James Knight --- sphinxcontrib/confluencebuilder/__main__.py | 3 ++- sphinxcontrib/confluencebuilder/cmd/build.py | 10 +++++----- sphinxcontrib/confluencebuilder/cmd/report.py | 10 +++++----- sphinxcontrib/confluencebuilder/cmd/wipe.py | 10 +++++----- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/sphinxcontrib/confluencebuilder/__main__.py b/sphinxcontrib/confluencebuilder/__main__.py index bcb44072..275cd70c 100644 --- a/sphinxcontrib/confluencebuilder/__main__.py +++ b/sphinxcontrib/confluencebuilder/__main__.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) +from pathlib import Path from sphinx.util.console import color_terminal from sphinx.util.console import nocolor # pylint: disable=no-name-in-module from sphinxcontrib.confluencebuilder import __version__ as version @@ -29,7 +30,7 @@ def main(): parser.add_argument('--verbose', '-V', action='count', default=0) parser.add_argument('--version', action='version', version='%(prog)s ' + version) - parser.add_argument('--work-dir') + parser.add_argument('--work-dir', type=Path) args, _ = parser.parse_known_args() if args.help: diff --git a/sphinxcontrib/confluencebuilder/cmd/build.py b/sphinxcontrib/confluencebuilder/cmd/build.py index ca4c51bb..8aba544a 100644 --- a/sphinxcontrib/confluencebuilder/cmd/build.py +++ b/sphinxcontrib/confluencebuilder/cmd/build.py @@ -2,10 +2,10 @@ # Copyright Sphinx Confluence Builder Contributors (AUTHORS) from contextlib import suppress +from pathlib import Path from sphinx.application import Sphinx from sphinx.util.docutils import docutils_namespace from sphinxcontrib.confluencebuilder.logger import ConfluenceLogger as logger -import os import sys #: default builder to invoke when one is not specified @@ -26,7 +26,7 @@ def build_main(args_parser): """ args_parser.add_argument('-D', action='append', default=[], dest='define') - args_parser.add_argument('--output-dir', '-o') + args_parser.add_argument('--output-dir', '-o', type=Path) known_args = sys.argv[1:] args, unknown_args = args_parser.parse_known_args(known_args) @@ -42,12 +42,12 @@ def build_main(args_parser): logger.error('invalid define provided in command line') return 1 - work_dir = args.work_dir if args.work_dir else os.getcwd() + work_dir = args.work_dir if args.work_dir else Path.cwd() if args.output_dir: output_dir = args.output_dir else: - output_dir = os.path.join(work_dir, '_build', 'confluence') - doctrees_dir = os.path.join(output_dir, '.doctrees') + output_dir = work_dir / '_build' / 'confluence' + doctrees_dir = output_dir / '.doctrees' builder = args.action if args.action else DEFAULT_BUILDER verbosity = 0 diff --git a/sphinxcontrib/confluencebuilder/cmd/report.py b/sphinxcontrib/confluencebuilder/cmd/report.py index 9c4b9d88..065178b2 100644 --- a/sphinxcontrib/confluencebuilder/cmd/report.py +++ b/sphinxcontrib/confluencebuilder/cmd/report.py @@ -3,6 +3,7 @@ from collections import OrderedDict from docutils import __version__ as docutils_version +from pathlib import Path from requests import __version__ as requests_version from sphinx import __version__ as sphinx_version from sphinx.application import Sphinx @@ -19,7 +20,6 @@ from urllib.parse import urlparse from urllib3 import __version__ as urllib3_version from xml.etree import ElementTree -import os import platform import sys import traceback @@ -68,7 +68,7 @@ def report_main(args_parser): rv = 0 offline = args.offline - work_dir = args.work_dir if args.work_dir else os.getcwd() + work_dir = args.work_dir if args.work_dir else Path.cwd() # setup sphinx engine to extract configuration config = {} @@ -81,8 +81,8 @@ def report_main(args_parser): print('fetching configuration information...') builder = ConfluenceReportBuilder.name app = Sphinx( - work_dir, # document sources - work_dir, # directory with configuration + str(work_dir), # document sources + str(work_dir), # directory with configuration tmp_dir, # output for built documents tmp_dir, # output for doctree files builder, # builder to execute @@ -126,7 +126,7 @@ def report_main(args_parser): sys.stdout.flush() tb_msg = traceback.format_exc() logger.error(tb_msg) - if os.path.isfile(os.path.join(work_dir, 'conf.py')): + if Path(work_dir / 'conf.py').is_file(): configuration_load_issue = 'unable to load configuration' configuration_load_issue += '\n\n' + tb_msg.strip() else: diff --git a/sphinxcontrib/confluencebuilder/cmd/wipe.py b/sphinxcontrib/confluencebuilder/cmd/wipe.py index 639f4e9c..44f8c146 100644 --- a/sphinxcontrib/confluencebuilder/cmd/wipe.py +++ b/sphinxcontrib/confluencebuilder/cmd/wipe.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) +from pathlib import Path from sphinx.application import Sphinx from sphinx.locale import __ from sphinx.util.docutils import docutils_namespace @@ -8,7 +9,6 @@ from sphinxcontrib.confluencebuilder.logger import ConfluenceLogger as logger from sphinxcontrib.confluencebuilder.publisher import ConfluencePublisher from sphinxcontrib.confluencebuilder.util import temp_dir -import os import sys import traceback @@ -34,7 +34,7 @@ def wipe_main(args_parser): if unknown_args: logger.warn('unknown arguments: {}'.format(' '.join(unknown_args))) - work_dir = args.work_dir if args.work_dir else os.getcwd() + work_dir = args.work_dir if args.work_dir else Path.cwd() # protection warning if not args.danger: @@ -63,8 +63,8 @@ def wipe_main(args_parser): try: with temp_dir() as tmp_dir, docutils_namespace(): app = Sphinx( - work_dir, # document sources - work_dir, # directory with configuration + str(work_dir), # document sources + str(work_dir), # directory with configuration tmp_dir, # output for built documents tmp_dir, # output for doctree files 'confluence', # builder to execute @@ -86,7 +86,7 @@ def wipe_main(args_parser): except Exception: # noqa: BLE001 sys.stdout.flush() logger.error(traceback.format_exc()) - if os.path.isfile(os.path.join(work_dir, 'conf.py')): + if Path(work_dir / 'conf.py').is_file(): logger.error('unable to load configuration') else: logger.error('no documentation/missing configuration') From 05555ffaf54f782fe83ecfeedab8d1026c233e97 Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 20:45:37 -0500 Subject: [PATCH 2/9] logger: manage tracefile using pathlib This is part of an ongoing maintenance task to the implementation to use newer capabilities of Python that are supported on the oldest interpreter version this extension supports. Signed-off-by: James Knight --- sphinxcontrib/confluencebuilder/logger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinxcontrib/confluencebuilder/logger.py b/sphinxcontrib/confluencebuilder/logger.py index 50084cc6..f18c6cf0 100644 --- a/sphinxcontrib/confluencebuilder/logger.py +++ b/sphinxcontrib/confluencebuilder/logger.py @@ -3,6 +3,7 @@ from collections import deque from contextlib import suppress +from pathlib import Path from sphinx.util import logging from sphinx.util.console import bold # pylint: disable=no-name-in-module import sys @@ -118,7 +119,8 @@ def trace(container, data): This is solely for manually debugging unexpected scenarios. """ try: - with open('trace.log', 'a', encoding='utf-8') as file: + trace_file = Path('trace.log') + with trace_file.open('a', encoding='utf-8') as file: file.write('[%s]\n' % container) file.write(data) file.write('\n') From 6baa67d3cdbcbe47b4ad9b0d0fcf03e419c4febb Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 20:54:13 -0500 Subject: [PATCH 3/9] util: update context-supported temp_dir to use pathlib This is part of an ongoing maintenance task to the implementation to use newer capabilities of Python that are supported on the oldest interpreter version this extension supports. Signed-off-by: James Knight --- sphinxcontrib/confluencebuilder/cmd/report.py | 4 ++-- sphinxcontrib/confluencebuilder/cmd/wipe.py | 4 ++-- sphinxcontrib/confluencebuilder/util.py | 2 +- tests/unit-tests/test_cache.py | 2 -- tests/unit-tests/test_legacy_pages.py | 2 -- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/sphinxcontrib/confluencebuilder/cmd/report.py b/sphinxcontrib/confluencebuilder/cmd/report.py index 065178b2..10607c4d 100644 --- a/sphinxcontrib/confluencebuilder/cmd/report.py +++ b/sphinxcontrib/confluencebuilder/cmd/report.py @@ -83,8 +83,8 @@ def report_main(args_parser): app = Sphinx( str(work_dir), # document sources str(work_dir), # directory with configuration - tmp_dir, # output for built documents - tmp_dir, # output for doctree files + str(tmp_dir), # output for built documents + str(tmp_dir), # output for doctree files builder, # builder to execute status=sys.stdout, # sphinx status output warning=sys.stderr) # sphinx warning output diff --git a/sphinxcontrib/confluencebuilder/cmd/wipe.py b/sphinxcontrib/confluencebuilder/cmd/wipe.py index 44f8c146..9c580475 100644 --- a/sphinxcontrib/confluencebuilder/cmd/wipe.py +++ b/sphinxcontrib/confluencebuilder/cmd/wipe.py @@ -65,8 +65,8 @@ def wipe_main(args_parser): app = Sphinx( str(work_dir), # document sources str(work_dir), # directory with configuration - tmp_dir, # output for built documents - tmp_dir, # output for doctree files + str(tmp_dir), # output for built documents + str(tmp_dir), # output for doctree files 'confluence', # builder to execute status=sys.stdout, # sphinx status output warning=sys.stderr) # sphinx warning output diff --git a/sphinxcontrib/confluencebuilder/util.py b/sphinxcontrib/confluencebuilder/util.py index 1aea30db..f81fc851 100644 --- a/sphinxcontrib/confluencebuilder/util.py +++ b/sphinxcontrib/confluencebuilder/util.py @@ -399,6 +399,6 @@ def temp_dir(): """ dir_ = tempfile.mkdtemp() try: - yield dir_ + yield Path(dir_) finally: shutil.rmtree(dir_, ignore_errors=True) diff --git a/tests/unit-tests/test_cache.py b/tests/unit-tests/test_cache.py index bfc897b6..ffe24b1f 100644 --- a/tests/unit-tests/test_cache.py +++ b/tests/unit-tests/test_cache.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) -from pathlib import Path from sphinxcontrib.confluencebuilder.util import temp_dir from tests.lib import prepare_dirs from tests.lib.testcase import ConfluenceTestCase @@ -98,7 +97,6 @@ def write_doc(file, data): pass with temp_dir() as src_dir: - src_dir = Path(src_dir) index_file = src_dir / 'index.rst' write_doc(index_file, '''\ index diff --git a/tests/unit-tests/test_legacy_pages.py b/tests/unit-tests/test_legacy_pages.py index 9495a36b..080b6985 100644 --- a/tests/unit-tests/test_legacy_pages.py +++ b/tests/unit-tests/test_legacy_pages.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) -from pathlib import Path from sphinxcontrib.confluencebuilder.builder import ConfluenceBuilder from sphinxcontrib.confluencebuilder.util import temp_dir from tests.lib import prepare_dirs @@ -39,7 +38,6 @@ def wrapped_init(builder): out_dir = prepare_dirs() with temp_dir() as src_dir: - src_dir = Path(src_dir) conf_file = src_dir / 'conf.py' write_doc(conf_file, '') From 160b3da7c3a40f73aa5821edda2f54277970f278 Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 21:06:00 -0500 Subject: [PATCH 4/9] translator: adjust doc-path processing (internal links) to use pathlib This is part of an ongoing maintenance task to the implementation to use newer capabilities of Python that are supported on the oldest interpreter version this extension supports. Signed-off-by: James Knight --- .../confluencebuilder/storage/translator.py | 17 ++++++++++------- sphinxcontrib/confluencebuilder/translator.py | 7 ++++--- tests/unit-tests/test_translator.py | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/sphinxcontrib/confluencebuilder/storage/translator.py b/sphinxcontrib/confluencebuilder/storage/translator.py index b52f9ccd..7a42380f 100644 --- a/sphinxcontrib/confluencebuilder/storage/translator.py +++ b/sphinxcontrib/confluencebuilder/storage/translator.py @@ -4,7 +4,7 @@ from contextlib import suppress from docutils import nodes -from os import path +from pathlib import Path from sphinx import addnodes from sphinx.locale import _ as SL from sphinx.locale import admonitionlabels @@ -1340,8 +1340,9 @@ def _visit_reference_intern_id(self, node): self._reference_context.append(self._end_ac_link_body(node)) def _visit_reference_intern_uri(self, node): - docname = posixpath.normpath( - self.docparent + path.splitext(node['refuri'].split('#')[0])[0]) + doc_path = Path(node['refuri'].split('#')[0]) + doc_raw_id = Path(self.docparent) / doc_path.parent / doc_path.stem + docname = posixpath.normpath(doc_raw_id.as_posix()) doctitle = self.state.title(docname) if not doctitle: self.warn('unable to build link to document due to ' @@ -2517,8 +2518,9 @@ def visit_confluence_doc_card(self, node): self.body.append(self._start_ac_macro(node, 'panel')) self.body.append(self._start_ac_rich_text_body_macro(node)) - docname = posixpath.normpath(self.docparent + - path.splitext(PARAMS(node)['href'].split('#')[0])[0]) + doc_path = Path(PARAMS(node)['href'].split('#')[0]) + doc_raw_id = Path(self.docparent) / doc_path.parent / doc_path.stem + docname = posixpath.normpath(doc_raw_id.as_posix()) doctitle = self.state.title(docname) if doctitle: attribs = {} @@ -2550,8 +2552,9 @@ def visit_confluence_doc_card(self, node): raise nodes.SkipNode def visit_confluence_doc_card_inline(self, node): - docname = posixpath.normpath(self.docparent + - path.splitext(node['reftarget'].split('#')[0])[0]) + doc_path = Path(node['reftarget'].split('#')[0]) + doc_raw_id = Path(self.docparent) / doc_path.parent / doc_path.stem + docname = posixpath.normpath(doc_raw_id.as_posix()) doctitle = self.state.title(docname) if doctitle: doctitle = self.encode(doctitle) diff --git a/sphinxcontrib/confluencebuilder/translator.py b/sphinxcontrib/confluencebuilder/translator.py index 6f753eaf..12f62bab 100644 --- a/sphinxcontrib/confluencebuilder/translator.py +++ b/sphinxcontrib/confluencebuilder/translator.py @@ -3,7 +3,7 @@ from docutils import nodes from docutils.nodes import NodeVisitor as BaseTranslator -from os import path +from pathlib import Path from sphinx.util.images import get_image_size from sphinx.util.images import guess_mimetype from sphinx.util.osutil import SEP @@ -49,9 +49,10 @@ def __init__(self, document, builder): # for relative document uris # (see '_visit_reference_intern_uri') if SEP in self.docname: - self.docparent = self.docname[0 : self.docname.rfind(SEP) + 1] + docparent = self.docname[0 : self.docname.rfind(SEP) + 1] else: - self.docparent = '' + docparent = '' + self.docparent = Path(docparent) self.assets = builder.assets self.body = [] diff --git a/tests/unit-tests/test_translator.py b/tests/unit-tests/test_translator.py index 7691ba12..8b6679f9 100644 --- a/tests/unit-tests/test_translator.py +++ b/tests/unit-tests/test_translator.py @@ -33,4 +33,4 @@ def test_translator_docname_and_docparent(self): translator = ConfluenceBaseTranslator(doc, app.builder) self.assertEqual(translator.docname, 'foo/bar/baz') - self.assertEqual(translator.docparent, 'foo/bar/') + self.assertEqual(translator.docparent.as_posix(), 'foo/bar') From dc6eaca851997d9d3b5c8253e721b5477eb9a926 Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 21:07:14 -0500 Subject: [PATCH 5/9] transmute: adjust sphinx-toolbox docname mocking to use pathlib This is part of an ongoing maintenance task to the implementation to use newer capabilities of Python that are supported on the oldest interpreter version this extension supports. Signed-off-by: James Knight --- .../confluencebuilder/transmute/ext_sphinx_toolbox.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinxcontrib/confluencebuilder/transmute/ext_sphinx_toolbox.py b/sphinxcontrib/confluencebuilder/transmute/ext_sphinx_toolbox.py index a3f05ba0..b54b492b 100644 --- a/sphinxcontrib/confluencebuilder/transmute/ext_sphinx_toolbox.py +++ b/sphinxcontrib/confluencebuilder/transmute/ext_sphinx_toolbox.py @@ -2,7 +2,7 @@ # Copyright Sphinx Confluence Builder Contributors (AUTHORS) from docutils import nodes -from os import path +from pathlib import Path from sphinx import addnodes from sphinxcontrib.confluencebuilder.compat import docutils_findall as findall from sphinxcontrib.confluencebuilder.nodes import confluence_expand @@ -68,11 +68,11 @@ def replace_sphinx_toolbox_nodes(builder, doctree): # directory; which the processing of a download_reference will # strip and use the asset directory has the container folder to find # the file in - mock_docname = path.join(builder.config.assets_dir, 'mock') + mock_docname = Path(builder.config.assets_dir, 'mock') new_node = addnodes.download_reference( node.astext(), node.astext(), - refdoc=mock_docname, + refdoc=str(mock_docname), refexplicit=True, reftarget=node['refuri'], ) From fcaf6f870dcb7c79c4f881c51601c96b254f4fe8 Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 21:09:38 -0500 Subject: [PATCH 6/9] adjust search/index page processing to use pathlib This is part of an ongoing maintenance task to the implementation to use newer capabilities of Python that are supported on the oldest interpreter version this extension supports. Signed-off-by: James Knight --- sphinxcontrib/confluencebuilder/storage/index.py | 14 ++++++++------ sphinxcontrib/confluencebuilder/storage/search.py | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/sphinxcontrib/confluencebuilder/storage/index.py b/sphinxcontrib/confluencebuilder/storage/index.py index 5f5a58fb..25431ce8 100644 --- a/sphinxcontrib/confluencebuilder/storage/index.py +++ b/sphinxcontrib/confluencebuilder/storage/index.py @@ -1,11 +1,11 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) +from pathlib import Path from sphinx.environment.adapters.indexentries import IndexEntries from sphinxcontrib.confluencebuilder.locale import L as sccb_translation from sphinxcontrib.confluencebuilder.state import ConfluenceState from sphinxcontrib.confluencebuilder.storage import intern_uri_anchor_value -import os import pkgutil import posixpath @@ -48,8 +48,8 @@ def generate_storage_format_domainindex(builder, docname, f): else: domainindex_fname = 'domainindex.html' - domainindex_template = os.path.join('templates', domainindex_fname) - template_data = pkgutil.get_data(__name__, domainindex_template) + domainindex_template = Path('templates', domainindex_fname) + template_data = pkgutil.get_data(__name__, str(domainindex_template)) # process the template with the generated index ctx = { @@ -96,8 +96,8 @@ def generate_storage_format_genindex(builder, docname, f): else: genindex_fname = 'genindex.html' - genindex_template = os.path.join('templates', genindex_fname) - template_data = pkgutil.get_data(__name__, genindex_template) + genindex_template = Path('templates', genindex_fname) + template_data = pkgutil.get_data(__name__, str(genindex_template)) # process the template with the generated index ctx = { @@ -125,7 +125,9 @@ def process_doclink(config, refuri): the document's title and anchor value """ - docname = posixpath.normpath(os.path.splitext(refuri.split('#')[0])[0]) + doc_path = Path(refuri.split('#')[0]) + doc_raw_id = doc_path.parent / doc_path.stem + docname = posixpath.normpath(doc_raw_id.as_posix()) doctitle = ConfluenceState.title(docname) anchor_value = intern_uri_anchor_value(docname, refuri) diff --git a/sphinxcontrib/confluencebuilder/storage/search.py b/sphinxcontrib/confluencebuilder/storage/search.py index b39fff52..cfdf2287 100644 --- a/sphinxcontrib/confluencebuilder/storage/search.py +++ b/sphinxcontrib/confluencebuilder/storage/search.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) +from pathlib import Path from sphinxcontrib.confluencebuilder.locale import L as sccb_translation -import os import pkgutil @@ -23,8 +23,8 @@ def generate_storage_format_search(builder, docname, f): space_name = builder.config.confluence_space_name # fetch raw template data - search_template = os.path.join('templates', 'search.html') - template_data = pkgutil.get_data(__name__, search_template) + search_template = Path('templates', 'search.html') + template_data = pkgutil.get_data(__name__, str(search_template)) # process the template with the generated index ctx = { From 336725c2bd869c344777205fc0cfdc018333e982 Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 21:29:16 -0500 Subject: [PATCH 7/9] adjust asset processing to use pathlib This is part of an ongoing maintenance task to the implementation to use newer capabilities of Python that are supported on the oldest interpreter version this extension supports. Signed-off-by: James Knight --- sphinxcontrib/confluencebuilder/assets.py | 30 +++++----- sphinxcontrib/confluencebuilder/builder.py | 6 +- sphinxcontrib/confluencebuilder/env.py | 2 +- sphinxcontrib/confluencebuilder/svg.py | 8 +-- sphinxcontrib/confluencebuilder/util.py | 66 ++++++++++++---------- 5 files changed, 60 insertions(+), 52 deletions(-) diff --git a/sphinxcontrib/confluencebuilder/assets.py b/sphinxcontrib/confluencebuilder/assets.py index 0d4bcf5e..53671fc0 100644 --- a/sphinxcontrib/confluencebuilder/assets.py +++ b/sphinxcontrib/confluencebuilder/assets.py @@ -2,6 +2,7 @@ # Copyright Sphinx Confluence Builder Contributors (AUTHORS) from docutils import nodes +from pathlib import Path from sphinx import addnodes from sphinx.util.osutil import canon_path from sphinx.util.images import guess_mimetype @@ -11,7 +12,6 @@ from sphinxcontrib.confluencebuilder.std.confluence import SUPPORTED_IMAGE_TYPES from sphinxcontrib.confluencebuilder.util import ConfluenceUtil from sphinxcontrib.confluencebuilder.util import find_env_abspath -import os # default content type to use if a type cannot be detected for an asset DEFAULT_CONTENT_TYPE = 'application/octet-stream' @@ -54,15 +54,15 @@ class ConfluenceAssetManager: Args: config: the active configuration env: the build environment - outdir: configured output directory (where assets may be stored) + out_dir: configured output directory (where assets may be stored) """ - def __init__(self, config, env, outdir): + def __init__(self, config, env, out_dir): self.assets = [] self.env = env self.force_standalone = config.confluence_asset_force_standalone self.hash2asset = {} self.keys = set() - self.outdir = outdir + self.out_dir = out_dir self.path2asset = {} self.root_doc = config.root_doc @@ -83,8 +83,8 @@ def add(self, path, docname): the key, document name and path """ logger.verbose('adding manual attachment: %s' % path) - abspath = find_env_abspath(self.env, self.outdir, path) - return self._handle_entry(abspath, docname, standalone=True) + abs_path = find_env_abspath(self.env, self.out_dir, path) + return self._handle_entry(abs_path, docname, standalone=True) def build(self): """ @@ -284,18 +284,18 @@ def _handle_entry(self, path, docname, standalone=False): hash_exists = hash_ in self.hash2asset if not hash_exists or standalone: # no asset entry and no hash entry (or standalone); new asset - key = os.path.basename(path) + key = path.name # Confluence does not allow attachments with select characters. # Filter out the asset name to a compatible key value. for rep in INVALID_CHARS: key = key.replace(rep, '_') - filename, file_ext = os.path.splitext(key) + new_path = Path(key) idx = 1 while key in self.keys: idx += 1 - key = f'{filename}_{idx}{file_ext}' + key = f'{new_path.stem}_{idx}{new_path.suffix}' self.keys.add(key) asset = ConfluenceAsset(key, path, type_, hash_) @@ -332,20 +332,20 @@ def _interpret_asset_path(self, node): path = None if isinstance(node, nodes.image): # uri's will be relative to documentation root. - path = str(node['uri']) + path = Path(node['uri']) elif isinstance(node, addnodes.download_reference): # reftarget will be a reference to the asset with respect to the # document (refdoc) holding this reference. Use reftarget and refdoc # to find a proper path. - docdir = os.path.dirname(node['refdoc']) - path = os.path.join(docdir, node['reftarget']) + docdir = Path(node['refdoc']).parent + path = docdir / node['reftarget'] - abspath = find_env_abspath(self.env, self.outdir, path) + abs_path = find_env_abspath(self.env, self.out_dir, path) - if not abspath: + if not abs_path: logger.verbose('failed to find path: %s' % path) - return abspath + return abs_path class ConfluenceSupportedImages: diff --git a/sphinxcontrib/confluencebuilder/builder.py b/sphinxcontrib/confluencebuilder/builder.py index cf305157..5d2ea8af 100644 --- a/sphinxcontrib/confluencebuilder/builder.py +++ b/sphinxcontrib/confluencebuilder/builder.py @@ -152,7 +152,7 @@ def init(self): if new_url: self.cloud = new_url.endswith('.atlassian.net/wiki/') - self.assets = ConfluenceAssetManager(config, self.env, self.outdir) + self.assets = ConfluenceAssetManager(config, self.env, self.out_dir) self.writer = ConfluenceWriter(self) self.config.sphinx_verbosity = self._verbose self.publisher.init(self.config, self.cloud) @@ -789,13 +789,13 @@ def to_asset_name(asset): for asset in status_iterator(assets, 'publishing assets... ', length=len(assets), verbosity=self._verbose, stringify_func=to_asset_name): - key, absfile, type_, hash_, docname = asset + key, abs_file, type_, hash_, docname = asset if self._check_publish_skip(docname): self.verbose(key + ' skipped due to configuration') continue try: - with open(absfile, 'rb') as file: + with abs_file.open('rb') as file: output = file.read() self.publish_asset(key, docname, output, type_, hash_) except OSError as err: diff --git a/sphinxcontrib/confluencebuilder/env.py b/sphinxcontrib/confluencebuilder/env.py index 4b28e61a..86596607 100644 --- a/sphinxcontrib/confluencebuilder/env.py +++ b/sphinxcontrib/confluencebuilder/env.py @@ -135,7 +135,7 @@ def track_page_hash(self, docname): return doc_hash # determine the hash of the document based on data + config-hash - src_file = self.env.doc2path(docname) + src_file = Path(self.env.doc2path(docname)) src_file_hash = ConfluenceUtil.hash_asset(src_file) doc_hash_data = self._active_hash + src_file_hash doc_hash = ConfluenceUtil.hash(doc_hash_data) diff --git a/sphinxcontrib/confluencebuilder/svg.py b/sphinxcontrib/confluencebuilder/svg.py index d68b807b..0d5cfbc1 100644 --- a/sphinxcontrib/confluencebuilder/svg.py +++ b/sphinxcontrib/confluencebuilder/svg.py @@ -60,17 +60,17 @@ def confluence_supported_svg(builder, node): return # invalid uri/path - uri_abspath = find_env_abspath(builder.env, builder.outdir, uri) - if not uri_abspath: + abs_path = find_env_abspath(builder.env, builder.out_dir, uri) + if not abs_path: return # ignore non-svgs - mimetype = guess_mimetype(uri_abspath) + mimetype = guess_mimetype(abs_path) if mimetype != 'image/svg+xml': return try: - with open(uri_abspath, 'rb') as f: + with abs_path.open('rb') as f: svg_data = f.read() except (IOError, OSError) as err: builder.warn('error reading svg: %s' % err) diff --git a/sphinxcontrib/confluencebuilder/util.py b/sphinxcontrib/confluencebuilder/util.py index f81fc851..5d6ba7ac 100644 --- a/sphinxcontrib/confluencebuilder/util.py +++ b/sphinxcontrib/confluencebuilder/util.py @@ -56,7 +56,7 @@ def hash_asset(asset): """ BLOCKSIZE = 65536 sha = sha256() - with open(asset, 'rb') as file: + with asset.open('rb') as file: buff = file.read(BLOCKSIZE) while len(buff) > 0: sha.update(buff) @@ -196,7 +196,7 @@ def extract_strings_from_file(filename): return filelist -def find_env_abspath(env, outdir, path): +def find_env_abspath(env, out_dir, path: str): """ find an existing absolute path for a provided path in a sphinx environment @@ -206,43 +206,51 @@ def find_env_abspath(env, outdir, path): Args: env: the build environment - outdir: the build's output directory + out_dir: the build's output directory path: the path to use Returns: the absolute path """ - abspath = None - if path: - path = os.path.normpath(path) - if os.path.isabs(path): - abspath = path - - # some generated nodes will prefix the path of an asset with `/`, - # with the intent of that path being the root from the - # configured source directory instead of an absolute path on the - # system -- check the environment's source directory first before - # checking the full system's path - if path[0] == os.sep: - new_path = os.path.join(env.srcdir, path[1:]) - - if os.path.isfile(new_path): - abspath = new_path - else: - abspath = os.path.join(env.srcdir, path) + if not path: + return None + + try: + normalized_path = Path(os.path.normpath(path)) + except OSError: + # ignore paths that may not resolve; this may include paths with + # with wildcard hints to be used for candidate fetching at a + # later stage + return None + + if normalized_path.is_absolute(): + abs_path = normalized_path + + # some generated nodes will prefix the path of an asset with `/`, + # with the intent of that path being the root from the + # configured source directory instead of an absolute path on the + # system -- check the environment's source directory first before + # checking the full system's path + if normalized_path.name[0] == os.sep: + new_path = Path(env.srcdir, normalized_path.name[1:]) + + if new_path.is_file(): + abs_path = new_path + else: + abs_path = Path(env.srcdir, normalized_path) - # extensions may dump a generated asset in the output directory; if - # the absolute mapping to the source directory does not find the - # asset, attempt to bind the path based on the output directory - if not os.path.isfile(abspath): - abspath = os.path.join(outdir, path) + # extensions may dump a generated asset in the output directory; if + # the absolute mapping to the source directory does not find the + # asset, attempt to bind the path based on the output directory + if not abs_path.is_file(): + abs_path = out_dir / normalized_path # if no asset can be found, ensure a `None` path is returned - if not os.path.isfile(abspath): - abspath = None + if not abs_path.is_file(): + abs_path = None - return abspath + return abs_path def first(it): From cdf6ac59ebd0f8e38b1c8b3deedd81f367f93f0c Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 21:33:57 -0500 Subject: [PATCH 8/9] adjust configuration processing to use pathlib This is part of an ongoing maintenance task to the implementation to use newer capabilities of Python that are supported on the oldest interpreter version this extension supports. Signed-off-by: James Knight --- sphinxcontrib/confluencebuilder/builder.py | 13 ++++++------- sphinxcontrib/confluencebuilder/config/checks.py | 3 ++- .../confluencebuilder/config/defaults.py | 16 ++++++++-------- sphinxcontrib/confluencebuilder/translator.py | 8 ++++---- sphinxcontrib/confluencebuilder/util.py | 5 +++-- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/sphinxcontrib/confluencebuilder/builder.py b/sphinxcontrib/confluencebuilder/builder.py index 5d2ea8af..ba62408f 100644 --- a/sphinxcontrib/confluencebuilder/builder.py +++ b/sphinxcontrib/confluencebuilder/builder.py @@ -6,7 +6,6 @@ from docutils import nodes from docutils.io import StringOutput from pathlib import Path -from os import path from sphinx import addnodes from sphinx import version_info as sphinx_version_info from sphinx.builders import Builder @@ -910,13 +909,13 @@ def _generate_special_document(self, docname, generator): header_template_data = '' if self.config.confluence_header_file is not None: - fname = path.join(self.env.srcdir, + header_file = Path(self.env.srcdir, self.config.confluence_header_file) try: - with open(fname, encoding='utf-8') as file: + with header_file.open(encoding='utf-8') as file: header_template_data = file.read() + '\n' except OSError as err: - self.warn(f'error reading file {fname}: {err}') + self.warn(f'error reading file {header_file}: {err}') # if no data is supplied, the file is plain text if self.config.confluence_header_data is None: @@ -932,13 +931,13 @@ def _generate_special_document(self, docname, generator): footer_template_data = '' if self.config.confluence_footer_file is not None: - fname = path.join(self.env.srcdir, + footer_file = Path(self.env.srcdir, self.config.confluence_footer_file) try: - with open(fname, encoding='utf-8') as file: + with footer_file.open(encoding='utf-8') as file: footer_template_data = file.read() + '\n' except OSError as err: - self.warn(f'error reading file {fname}: {err}') + self.warn(f'error reading file {footer_file}: {err}') # if no data is supplied, the file is plain text if self.config.confluence_footer_data is None: diff --git a/sphinxcontrib/confluencebuilder/config/checks.py b/sphinxcontrib/confluencebuilder/config/checks.py index c77ef93b..a71e8052 100644 --- a/sphinxcontrib/confluencebuilder/config/checks.py +++ b/sphinxcontrib/confluencebuilder/config/checks.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) +from pathlib import Path from sphinxcontrib.confluencebuilder.config.notifications import deprecated from sphinxcontrib.confluencebuilder.config.notifications import warnings from sphinxcontrib.confluencebuilder.config.validation import ConfigurationValidation @@ -170,7 +171,7 @@ def validate_configuration(builder): ''') for cert in cert_files: - if cert and not os.path.isfile(os.path.join(env.srcdir, cert)): + if cert and not Path(env.srcdir, cert).is_file(): raise ConfluenceConfigurationError('''\ confluence_client_cert missing certificate file diff --git a/sphinxcontrib/confluencebuilder/config/defaults.py b/sphinxcontrib/confluencebuilder/config/defaults.py index 86655e5b..cc04c526 100644 --- a/sphinxcontrib/confluencebuilder/config/defaults.py +++ b/sphinxcontrib/confluencebuilder/config/defaults.py @@ -1,9 +1,9 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) +from pathlib import Path from sphinxcontrib.confluencebuilder.debug import PublishDebug from sphinxcontrib.confluencebuilder.util import str2bool -import os # configures the default editor to publication @@ -43,13 +43,13 @@ def apply_defaults(builder): if conf.confluence_adv_restricted is None: conf.confluence_adv_restricted = [] - if conf.confluence_ca_cert and not os.path.isabs(conf.confluence_ca_cert): - # if the ca path is not an absolute path, the path is a relative - # path based on the source directory (i.e. passed configuration - # checks); resolve the file here before it eventually gets provided - # to Requests - conf.confluence_ca_cert = os.path.join( - env.srcdir, conf.confluence_ca_cert) + if conf.confluence_ca_cert: + if not Path(conf.confluence_ca_cert).is_absolute(): + # if the ca path is not an absolute path, the path is a relative + # path based on the source directory (i.e. passed configuration + # checks); resolve the file here before it eventually gets provided + # to Requests + conf.confluence_ca_cert = Path(env.srcdir, conf.confluence_ca_cert) if conf.confluence_cleanup_search_mode is None: # the default is `search`, since on Confluence Server/DC; the `direct` diff --git a/sphinxcontrib/confluencebuilder/translator.py b/sphinxcontrib/confluencebuilder/translator.py index 12f62bab..a10dac62 100644 --- a/sphinxcontrib/confluencebuilder/translator.py +++ b/sphinxcontrib/confluencebuilder/translator.py @@ -89,10 +89,10 @@ def depart_document(self, node): # prepend header (if any) if self.builder.config.confluence_header_file is not None: header_template_data = '' - header_file = path.join(self.builder.env.srcdir, + header_file = Path(self.builder.env.srcdir, self.builder.config.confluence_header_file) try: - with open(header_file, encoding='utf-8') as file: + with header_file.open(encoding='utf-8') as file: header_template_data = file.read() except (IOError, OSError) as err: self.warn(f'error reading file {header_file}: {err}') @@ -114,10 +114,10 @@ def depart_document(self, node): # append footer (if any) if self.builder.config.confluence_footer_file is not None: footer_template_data = '' - footer_file = path.join(self.builder.env.srcdir, + footer_file = Path(self.builder.env.srcdir, self.builder.config.confluence_footer_file) try: - with open(footer_file, encoding='utf-8') as file: + with footer_file.open(encoding='utf-8') as file: footer_template_data = file.read() except (IOError, OSError) as err: self.warn(f'error reading file {footer_file}: {err}') diff --git a/sphinxcontrib/confluencebuilder/util.py b/sphinxcontrib/confluencebuilder/util.py index 5d6ba7ac..d646d253 100644 --- a/sphinxcontrib/confluencebuilder/util.py +++ b/sphinxcontrib/confluencebuilder/util.py @@ -185,8 +185,9 @@ def extract_strings_from_file(filename): """ filelist = [] - if os.path.isfile(filename): - with open(filename) as f: + file = Path(filename) if isinstance(filename, str) else filename + if file.is_file(): + with file.open() as f: for raw_line in f: line = raw_line.strip() if not line or line.startswith('#'): From 6f4902db534b920fb625acb61ce2b787e8bd2837 Mon Sep 17 00:00:00 2001 From: James Knight Date: Mon, 26 Feb 2024 21:45:46 -0500 Subject: [PATCH 9/9] str-cast paths for sphinx 6.1.x's guess_mimetype Sphinx's `guess_mimetype` for v6.1.x series requires a string type value (i.e. not a path-like object). Adding an explicit cast while we still support this version. Signed-off-by: James Knight --- sphinxcontrib/confluencebuilder/assets.py | 3 ++- sphinxcontrib/confluencebuilder/svg.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sphinxcontrib/confluencebuilder/assets.py b/sphinxcontrib/confluencebuilder/assets.py index 53671fc0..4ba3307d 100644 --- a/sphinxcontrib/confluencebuilder/assets.py +++ b/sphinxcontrib/confluencebuilder/assets.py @@ -274,7 +274,8 @@ def _handle_entry(self, path, docname, standalone=False): if path not in self.path2asset: hash_ = ConfluenceUtil.hash_asset(path) - type_ = guess_mimetype(path, default=DEFAULT_CONTENT_TYPE) + # str-cast for sphinx-6.1 + type_ = guess_mimetype(str(path), default=DEFAULT_CONTENT_TYPE) else: hash_ = self.path2asset[path].hash type_ = self.path2asset[path].type diff --git a/sphinxcontrib/confluencebuilder/svg.py b/sphinxcontrib/confluencebuilder/svg.py index 0d5cfbc1..4f3e28b7 100644 --- a/sphinxcontrib/confluencebuilder/svg.py +++ b/sphinxcontrib/confluencebuilder/svg.py @@ -65,7 +65,7 @@ def confluence_supported_svg(builder, node): return # ignore non-svgs - mimetype = guess_mimetype(abs_path) + mimetype = guess_mimetype(str(abs_path)) # cast for sphinx-6.1 if mimetype != 'image/svg+xml': return