diff --git a/sphinxcontrib/confluencebuilder/builder.py b/sphinxcontrib/confluencebuilder/builder.py index a22fdb5a..cf305157 100644 --- a/sphinxcontrib/confluencebuilder/builder.py +++ b/sphinxcontrib/confluencebuilder/builder.py @@ -41,6 +41,7 @@ from sphinxcontrib.confluencebuilder.util import first from sphinxcontrib.confluencebuilder.util import handle_cli_file_subset from sphinxcontrib.confluencebuilder.writer import ConfluenceWriter +import os import tempfile @@ -211,7 +212,7 @@ def prepare_subset(option): if value is None: return None - if isinstance(value, str): + if isinstance(value, (str, os.PathLike)): files = extract_strings_from_file(value) else: files = value diff --git a/sphinxcontrib/confluencebuilder/config/checks.py b/sphinxcontrib/confluencebuilder/config/checks.py index e22f85fb..c77ef93b 100644 --- a/sphinxcontrib/confluencebuilder/config/checks.py +++ b/sphinxcontrib/confluencebuilder/config/checks.py @@ -278,7 +278,6 @@ def validate_configuration(builder): # confluence_footer_file try: validator.conf('confluence_footer_file') \ - .string() \ .file() except ConfluenceConfigurationError as e: raise ConfluenceConfigurationError('''\ @@ -315,7 +314,6 @@ def validate_configuration(builder): # confluence_header_file try: validator.conf('confluence_header_file') \ - .string() \ .file() except ConfluenceConfigurationError as e: raise ConfluenceConfigurationError('''\ @@ -534,16 +532,15 @@ def validate_configuration(builder): # if provided a file via command line, treat as a list def conf_translate(value): return handle_cli_file_subset(builder, option, value) + value = conf_translate(value) + option_val = validator.conf(option, conf_translate) try: - validator.conf(option, conf_translate) \ - .string_or_strings() - - if isinstance(value, str): - validator.docnames_from_file() + if isinstance(value, (str, os.PathLike)): + option_val.docnames_from_file() else: - validator.docnames() + option_val.docnames() except ConfluenceConfigurationError as e: raise ConfluenceConfigurationError('''\ {msg} diff --git a/sphinxcontrib/confluencebuilder/config/validation.py b/sphinxcontrib/confluencebuilder/config/validation.py index 38117861..fcd0eafa 100644 --- a/sphinxcontrib/confluencebuilder/config/validation.py +++ b/sphinxcontrib/confluencebuilder/config/validation.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright Sphinx Confluence Builder Contributors (AUTHORS) +from pathlib import Path from sphinxcontrib.confluencebuilder.exceptions import ConfluenceConfigurationError from sphinxcontrib.confluencebuilder.util import extract_strings_from_file from sphinxcontrib.confluencebuilder.util import str2bool @@ -152,15 +153,14 @@ def docnames(self): value = self._value() if value is not None: - if not (isinstance(value, (list, set, tuple)) or not all( - isinstance(label, str) for label in value)): + if not (isinstance(value, (list, set, tuple))) or not all( + isinstance(label, (str, os.PathLike)) for label in value): raise ConfluenceConfigurationError( '%s is not a collection of filenames' % self.key) for docname in value: if not any( - os.path.isfile( - os.path.join(self.env.srcdir, docname + suffix)) + Path(self.env.srcdir, docname + suffix).is_file() for suffix in self.config.source_suffix): raise ConfluenceConfigurationError( f'{self.key} is missing document {docname}') @@ -186,16 +186,12 @@ def docnames_from_file(self): value = self._value() if value is not None: - if not isinstance(value, str) or not os.path.isfile( - os.path.join(self.env.srcdir, value)): - raise ConfluenceConfigurationError( - '%s is not a file' % self.key) + self.file() docnames = extract_strings_from_file(value) for docname in docnames: if not any( - os.path.isfile( - os.path.join(self.env.srcdir, docname + suffix)) + Path(self.env.srcdir, docname + suffix).is_file() for suffix in self.config.source_suffix): raise ConfluenceConfigurationError( f'{self.key} is missing document {docname}') @@ -247,8 +243,8 @@ def file(self): value = self._value() if value is not None: - if not isinstance(value, str) or not os.path.isfile( - os.path.join(self.env.srcdir, value)): + if not isinstance(value, (str, os.PathLike)) or \ + not Path(self.env.srcdir, value).is_file(): raise ConfluenceConfigurationError( '%s is not a file' % self.key) @@ -378,8 +374,8 @@ def path(self): value = self._value() if value is not None: - if not isinstance(value, str) or not os.path.exists( - os.path.join(self.env.srcdir, value)): + if not isinstance(value, (str, os.PathLike)) or \ + not Path(self.env.srcdir, value).exists(): raise ConfluenceConfigurationError( '%s is not a path' % self.key) diff --git a/sphinxcontrib/confluencebuilder/util.py b/sphinxcontrib/confluencebuilder/util.py index cf0d6be8..1aea30db 100644 --- a/sphinxcontrib/confluencebuilder/util.py +++ b/sphinxcontrib/confluencebuilder/util.py @@ -2,6 +2,7 @@ # Copyright Sphinx Confluence Builder Contributors (AUTHORS) from contextlib import contextmanager +from pathlib import Path from sphinxcontrib.confluencebuilder.std.confluence import API_REST_BIND_PATH from sphinxcontrib.confluencebuilder.std.confluence import FONT_SIZE from sphinxcontrib.confluencebuilder.std.confluence import FONT_X_HEIGHT @@ -321,19 +322,19 @@ def handle_cli_file_subset(builder, option, value): the resolved configuration value """ - if option in builder.config['overrides'] and isinstance(value, str): + if option in builder.config['overrides'] and \ + isinstance(value, (str, os.PathLike)): if not value: # an empty command line subset is an "unset" request # (and not an empty list); if no values are detected, # return `None` return None else: - if os.path.isabs(value): - target_file = value - else: - target_file = os.path.join(builder.env.srcdir, value) + target_file = Path(value) + if not target_file.is_absolute(): + target_file = Path(builder.env.srcdir, value) - if os.path.isfile(target_file): + if target_file.is_file(): value = target_file else: value = value.split(',') diff --git a/tests/unit-tests/test_config_checks.py b/tests/unit-tests/test_config_checks.py index bfceaa45..7e0e2c63 100644 --- a/tests/unit-tests/test_config_checks.py +++ b/tests/unit-tests/test_config_checks.py @@ -188,27 +188,40 @@ def test_config_check_additional_mime_types(self): self._try_config() def test_config_check_ca_cert(self): - valid_cert_dir = str(self.test_dir) - valid_cert_file = str(self.dummy_exists) - missing_cert = str(self.dummy_missing) + valid_cert_dir = self.test_dir + valid_cert_file = self.dummy_exists + missing_cert = self.dummy_missing self.config['confluence_ca_cert'] = valid_cert_dir self._try_config() + self.config['confluence_ca_cert'] = str(valid_cert_dir) + self._try_config() + self.config['confluence_ca_cert'] = valid_cert_file self._try_config() + self.config['confluence_ca_cert'] = str(valid_cert_file) + self._try_config() + self.config['confluence_ca_cert'] = missing_cert with self.assertRaises(ConfluenceConfigurationError): self._try_config() + self.config['confluence_ca_cert'] = str(missing_cert) + with self.assertRaises(ConfluenceConfigurationError): + self._try_config() + def test_config_check_client_cert(self): - valid_cert = str(self.dummy_exists) - missing_cert = str(self.dummy_missing) + valid_cert = self.dummy_exists + missing_cert = self.dummy_missing self.config['confluence_client_cert'] = valid_cert self._try_config() + self.config['confluence_client_cert'] = str(valid_cert) + self._try_config() + self.config['confluence_client_cert'] = (valid_cert, valid_cert) self._try_config() @@ -216,6 +229,10 @@ def test_config_check_client_cert(self): with self.assertRaises(ConfluenceConfigurationError): self._try_config() + self.config['confluence_client_cert'] = str(missing_cert) + with self.assertRaises(ConfluenceConfigurationError): + self._try_config() + self.config['confluence_client_cert'] = (valid_cert, missing_cert) with self.assertRaises(ConfluenceConfigurationError): self._try_config() @@ -350,16 +367,23 @@ def mock_transform(docname): self._try_config() def test_config_check_footer_file(self): - valid_footer = str(self.dummy_exists) - missing_footer = str(self.dummy_missing) + valid_footer = self.dummy_exists + missing_footer = self.dummy_missing self.config['confluence_footer_file'] = valid_footer self._try_config() + self.config['confluence_footer_file'] = str(valid_footer) + self._try_config() + self.config['confluence_footer_file'] = missing_footer with self.assertRaises(ConfluenceConfigurationError): self._try_config() + self.config['confluence_footer_file'] = str(missing_footer) + with self.assertRaises(ConfluenceConfigurationError): + self._try_config() + relbase = '../../templates/' self.config['confluence_footer_file'] = relbase + 'sample-footer.tpl' self._try_config() @@ -373,16 +397,23 @@ def test_config_check_global_labels(self): self._try_config() def test_config_check_header_file(self): - valid_header = str(self.dummy_exists) - missing_header = str(self.dummy_missing) + valid_header = self.dummy_exists + missing_header = self.dummy_missing self.config['confluence_header_file'] = valid_header self._try_config() + self.config['confluence_header_file'] = str(valid_header) + self._try_config() + self.config['confluence_header_file'] = missing_header with self.assertRaises(ConfluenceConfigurationError): self._try_config() + self.config['confluence_header_file'] = str(missing_header) + with self.assertRaises(ConfluenceConfigurationError): + self._try_config() + relbase = '../../templates/' self.config['confluence_header_file'] = relbase + 'sample-header.tpl' self._try_config() @@ -671,6 +702,9 @@ def test_config_check_publish_list(self): # file with a valid document list self.assertTrue(valid_list.is_file()) + self.config[option] = valid_list + self._try_config(dataset=dataset) + self.config[option] = str(valid_list) self._try_config(dataset=dataset) @@ -691,12 +725,20 @@ def test_config_check_publish_list(self): # file with invalid document list self.assertTrue(invalid_list.is_file()) + self.config[option] = invalid_list + with self.assertRaises(ConfluenceConfigurationError): + self._try_config(dataset=dataset) + self.config[option] = str(invalid_list) with self.assertRaises(ConfluenceConfigurationError): self._try_config(dataset=dataset) # missing file self.assertFalse(missing_list.is_file()) + self.config[option] = missing_list + with self.assertRaises(ConfluenceConfigurationError): + self._try_config(dataset=dataset) + self.config[option] = str(missing_list) with self.assertRaises(ConfluenceConfigurationError): self._try_config(dataset=dataset) diff --git a/tests/unit-tests/test_config_header_footer.py b/tests/unit-tests/test_config_header_footer.py index b71591a8..b98baa43 100644 --- a/tests/unit-tests/test_config_header_footer.py +++ b/tests/unit-tests/test_config_header_footer.py @@ -18,8 +18,8 @@ def test_storage_config_headerfooter_absolute(self): config = dict(self.config) footer_tpl = self.templates_dir / 'sample-footer.tpl' header_tpl = self.templates_dir / 'sample-header.tpl' - config['confluence_footer_file'] = str(footer_tpl) - config['confluence_header_file'] = str(header_tpl) + config['confluence_footer_file'] = footer_tpl + config['confluence_header_file'] = header_tpl out_dir = self.build(self.dataset, config=config) diff --git a/tests/unit-tests/test_config_publish_list.py b/tests/unit-tests/test_config_publish_list.py index efb4ccc8..998e9338 100644 --- a/tests/unit-tests/test_config_publish_list.py +++ b/tests/unit-tests/test_config_publish_list.py @@ -67,7 +67,7 @@ def test_config_publishlist_allow_list_cli_empty(self): ], any_order=True) def test_config_publishlist_allow_list_file_default_abs(self): - publish_list = str(self.dataset / 'publish-list-default') + publish_list = self.dataset / 'publish-list-default' config = dict(self.config) config['confluence_publish_allowlist'] = publish_list @@ -93,7 +93,7 @@ def test_config_publishlist_allow_list_file_default_relative(self): ], any_order=True) def test_config_publishlist_allow_list_file_empty(self): - publish_list = str(self.dataset / 'publish-list-empty') + publish_list = self.dataset / 'publish-list-empty' config = dict(self.config) config['confluence_publish_allowlist'] = publish_list @@ -198,7 +198,7 @@ def test_config_publishlist_deny_list_cli_empty(self): ], any_order=True) def test_config_publishlist_deny_list_file_default_abs(self): - publish_list = str(self.dataset / 'publish-list-default') + publish_list = self.dataset / 'publish-list-default' config = dict(self.config) config['confluence_publish_denylist'] = publish_list @@ -222,7 +222,7 @@ def test_config_publishlist_deny_list_file_default_relative(self): ], any_order=True) def test_config_publishlist_deny_list_file_empty(self): - publish_list = str(self.dataset / 'publish-list-empty') + publish_list = self.dataset / 'publish-list-empty' config = dict(self.config) config['confluence_publish_denylist'] = publish_list diff --git a/tests/unit-tests/test_sdoc_genindex.py b/tests/unit-tests/test_sdoc_genindex.py index 6685b0d0..6943221b 100644 --- a/tests/unit-tests/test_sdoc_genindex.py +++ b/tests/unit-tests/test_sdoc_genindex.py @@ -85,8 +85,8 @@ def test_storage_sdoc_genindex_header_footer(self): # defined header/footer data is also injected into the document. dataset = self.datasets / 'sdoc' / 'genindex' - footer_tpl = str(self.templates_dir / 'sample-footer.tpl') - header_tpl = str(self.templates_dir / 'sample-header.tpl') + footer_tpl = self.templates_dir / 'sample-footer.tpl' + header_tpl = self.templates_dir / 'sample-header.tpl' config = dict(self.config) config['confluence_use_index'] = True diff --git a/tests/unit-tests/test_sdoc_modindex.py b/tests/unit-tests/test_sdoc_modindex.py index 0384233b..ba303df5 100644 --- a/tests/unit-tests/test_sdoc_modindex.py +++ b/tests/unit-tests/test_sdoc_modindex.py @@ -95,8 +95,8 @@ def test_storage_sdoc_modindex_header_footer(self): # defined header/footer data is also injected into the document. dataset = self.datasets / 'sdoc' / 'py-modindex' - footer_tpl = str(self.templates_dir / 'sample-footer.tpl') - header_tpl = str(self.templates_dir / 'sample-header.tpl') + footer_tpl = self.templates_dir / 'sample-footer.tpl' + header_tpl = self.templates_dir / 'sample-header.tpl' config = dict(self.config) config['confluence_domain_indices'] = True diff --git a/tests/unit-tests/test_sdoc_search.py b/tests/unit-tests/test_sdoc_search.py index d68336ba..3a5c2490 100644 --- a/tests/unit-tests/test_sdoc_search.py +++ b/tests/unit-tests/test_sdoc_search.py @@ -63,8 +63,8 @@ def test_storage_sdoc_search_header_footer(self): # defined header/footer data is also injected into the document. dataset = self.datasets / 'minimal' - footer_tpl = str(self.templates_dir / 'sample-footer.tpl') - header_tpl = str(self.templates_dir / 'sample-header.tpl') + footer_tpl = self.templates_dir / 'sample-footer.tpl' + header_tpl = self.templates_dir / 'sample-header.tpl' config = dict(self.config) config['confluence_include_search'] = True