diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst index 60e66edbd13..d50abd89e92 100644 --- a/doc/usage/extensions/autosummary.rst +++ b/doc/usage/extensions/autosummary.rst @@ -195,6 +195,15 @@ also use these config values: .. versionadded:: 2.1 +.. confval:: autosummary_filename_map + + A dict mapping object names to filenames. This is necessary to avoid + filename conflicts where multiple objects have names that are + indistinguishable when case is ignored, on file systems where filenames + are case-insensitive. + + .. versionadded:: 3.2 + Customizing templates --------------------- diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 0984377c565..350604387b0 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -252,7 +252,9 @@ def run(self) -> List[Node]: tree_prefix = self.options['toctree'].strip() docnames = [] excluded = Matcher(self.config.exclude_patterns) + filename_map = self.config.autosummary_filename_map for name, sig, summary, real_name in items: + real_name = filename_map.get(real_name, real_name) docname = posixpath.join(tree_prefix, real_name) docname = posixpath.normpath(posixpath.join(dirname, docname)) if docname not in self.env.found_docs: @@ -785,6 +787,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_role('autolink', AutoLink()) app.connect('builder-inited', process_generate_options) app.add_config_value('autosummary_context', {}, True) + app.add_config_value('autosummary_filename_map', {}, 'html') app.add_config_value('autosummary_generate', [], True, [bool]) app.add_config_value('autosummary_generate_overwrite', True, False) app.add_config_value('autosummary_mock_imports', diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index d908e208822..c1b50de577a 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -74,6 +74,7 @@ def __init__(self, translator: NullTranslations) -> None: self.warningiserror = False self.config.add('autosummary_context', {}, True, None) + self.config.add('autosummary_filename_map', {}, True, None) self.config.init_values() def emit_firstresult(self, *args: Any) -> None: @@ -393,6 +394,11 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, # keep track of new files new_files = [] + if app: + filename_map = app.config.autosummary_filename_map + else: + filename_map = {} + # write for entry in sorted(set(items), key=str): if entry.path is None: @@ -418,7 +424,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, imported_members, app, entry.recursive, context, modname, qualname) - filename = os.path.join(path, name + suffix) + filename = os.path.join(path, filename_map.get(name, name) + suffix) if os.path.isfile(filename): with open(filename, encoding=encoding) as f: old_content = f.read() diff --git a/tests/roots/test-ext-autosummary-filename-map/autosummary_dummy_module.py b/tests/roots/test-ext-autosummary-filename-map/autosummary_dummy_module.py new file mode 100644 index 00000000000..1f57eeb256c --- /dev/null +++ b/tests/roots/test-ext-autosummary-filename-map/autosummary_dummy_module.py @@ -0,0 +1,21 @@ +from os import path # NOQA +from typing import Union + + +class Foo: + class Bar: + pass + + def __init__(self): + pass + + def bar(self): + pass + + @property + def baz(self): + pass + + +def bar(x: Union[int, str], y: int = 1) -> None: + pass diff --git a/tests/roots/test-ext-autosummary-filename-map/conf.py b/tests/roots/test-ext-autosummary-filename-map/conf.py new file mode 100644 index 00000000000..17e2fa44565 --- /dev/null +++ b/tests/roots/test-ext-autosummary-filename-map/conf.py @@ -0,0 +1,11 @@ +import os +import sys + +sys.path.insert(0, os.path.abspath('.')) + +extensions = ['sphinx.ext.autosummary'] +autosummary_generate = True +autosummary_filename_map = { + "autosummary_dummy_module": "module_mangled", + "autosummary_dummy_module.bar": "bar" +} diff --git a/tests/roots/test-ext-autosummary-filename-map/index.rst b/tests/roots/test-ext-autosummary-filename-map/index.rst new file mode 100644 index 00000000000..57d902b6aba --- /dev/null +++ b/tests/roots/test-ext-autosummary-filename-map/index.rst @@ -0,0 +1,9 @@ + +.. autosummary:: + :toctree: generated + :caption: An autosummary + + autosummary_dummy_module + autosummary_dummy_module.Foo + autosummary_dummy_module.Foo.bar + autosummary_dummy_module.bar diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index ede19f632d2..567a8caeaa5 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -394,6 +394,20 @@ def test_autosummary_recursive(app, status, warning): assert 'package.package.module' in content +@pytest.mark.sphinx('dummy', testroot='ext-autosummary-filename-map') +def test_autosummary_filename_map(app, status, warning): + app.build() + + assert (app.srcdir / 'generated' / 'module_mangled.rst').exists() + assert not (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').exists() + assert (app.srcdir / 'generated' / 'bar.rst').exists() + assert not (app.srcdir / 'generated' / 'autosummary_dummy_module.bar.rst').exists() + assert (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.rst').exists() + + html_warnings = app._warning.getvalue() + assert html_warnings == '' + + @pytest.mark.sphinx('latex', **default_kw) def test_autosummary_latex_table_colspec(app, status, warning): app.builder.build_all()