From 6b16e02de49f989867dd172898ff6b4e68388548 Mon Sep 17 00:00:00 2001 From: Brandon Wiebe <37316036+BrandonWiebe@users.noreply.github.com> Date: Tue, 28 Mar 2023 09:13:28 -0700 Subject: [PATCH] Let Sphinx handle adding domain directives to the toctree (#374) * Remove ability to add domains to the toctree * Add towncrier news fragment --- autoapi/extension.py | 5 - autoapi/toctree.py | 164 ------------------------------ docs/changes/+toctree.removal.rst | 2 + setup.cfg | 2 +- tests/test_integration.py | 4 +- 5 files changed, 6 insertions(+), 171 deletions(-) delete mode 100644 autoapi/toctree.py create mode 100644 docs/changes/+toctree.removal.rst diff --git a/autoapi/extension.py b/autoapi/extension.py index c16285f..d18ed08 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -26,7 +26,6 @@ from .backends import ( from .directives import AutoapiSummary, NestedParse from .inheritance_diagrams import AutoapiInheritanceDiagram from .settings import API_ROOT -from .toctree import add_domain_to_toctree LOGGER = sphinx.util.logging.getLogger(__name__) @@ -183,9 +182,6 @@ def doctree_read(app, doctree): Inject AutoAPI into the TOC Tree dynamically. """ - if app.config.autoapi_add_objects_to_toctree: - add_domain_to_toctree(app, doctree, app.env.docname) - if app.env.docname == "index": all_docs = set() insert = True @@ -296,7 +292,6 @@ def setup(app): app.add_config_value("autoapi_python_class_content", "class", "html") app.add_config_value("autoapi_generate_api_docs", True, "html") app.add_config_value("autoapi_prepare_jinja_env", None, "html") - app.add_config_value("autoapi_add_objects_to_toctree", True, "html") app.add_autodocumenter(documenters.AutoapiFunctionDocumenter) app.add_autodocumenter(documenters.AutoapiPropertyDocumenter) app.add_autodocumenter(documenters.AutoapiDecoratorDocumenter) diff --git a/autoapi/toctree.py b/autoapi/toctree.py deleted file mode 100644 index 9c9e059..0000000 --- a/autoapi/toctree.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -A small Sphinx extension that adds Domain objects (eg. Python Classes & Methods) to the TOC Tree. - -It dynamically adds them to the already rendered ``app.env.tocs`` dict on the Sphinx environment. -Traditionally this only contains Section's, -we then nest our Domain references inside the already existing Sections. -""" - -from docutils import nodes -from sphinx import addnodes -import sphinx.util.logging - -LOGGER = sphinx.util.logging.getLogger(__name__) - - -def _build_toc_node(docname, anchor="anchor", text="test text", bullet=False): - """ - Create the node structure that Sphinx expects for TOC Tree entries. - - The ``bullet`` argument wraps it in a ``nodes.bullet_list``, - which is how you nest TOC Tree entries. - """ - reference = nodes.reference( - "", - "", - internal=True, - refuri=docname, - anchorname="#" + anchor, - *[nodes.Text(text, text)] - ) - para = addnodes.compact_paragraph("", "", reference) - ret_list = nodes.list_item("", para) - return nodes.bullet_list("", ret_list) if bullet else ret_list - - -def _traverse_parent(node, objtypes): - """ - Traverse up the node's parents until you hit the ``objtypes`` referenced. - - Can either be a single type, - or a tuple of types. - """ - curr_node = node.parent - while curr_node is not None: - if isinstance(curr_node, objtypes): - return curr_node - curr_node = curr_node.parent - return None - - -def _find_toc_node(toc, ref_id, objtype): - """ - Find the actual TOC node for a ref_id. - - Depends on the object type: - * Section - First section (refuri) or 2nd+ level section (anchorname) - * Desc - Just use the anchor name - """ - for check_node in toc.traverse(nodes.reference): - if objtype == nodes.section and ( - check_node.attributes["refuri"] == ref_id - or check_node.attributes["anchorname"] == "#" + ref_id - ): - return check_node - if ( - objtype == addnodes.desc - and check_node.attributes["anchorname"] == "#" + ref_id - ): - return check_node - return None - - -def _get_toc_reference(node, toc, docname): - """ - Logic that understands maps a specific node to it's part of the toctree. - - It takes a specific incoming ``node``, - and returns the actual TOC Tree node that is said reference. - """ - if isinstance(node, nodes.section) and isinstance(node.parent, nodes.document): - # Top Level Section header - ref_id = docname - toc_reference = _find_toc_node(toc, ref_id, nodes.section) - elif isinstance(node, nodes.section): - # Nested Section header - ref_id = node.attributes["ids"][0] - toc_reference = _find_toc_node(toc, ref_id, nodes.section) - else: - # Desc node - try: - ref_id = node.children[0].attributes["ids"][0] - toc_reference = _find_toc_node(toc, ref_id, addnodes.desc) - except (KeyError, IndexError): - LOGGER.warning( - "Invalid desc node", - exc_info=True, - type="autoapi", - subtype="toc_reference", - ) - toc_reference = None - - return toc_reference - - -def add_domain_to_toctree(app, doctree, docname): - """ - Add domain objects to the toctree dynamically. - - This should be attached to the ``doctree-resolved`` event. - This works by: - - * Finding each domain node (addnodes.desc) - * Figuring out it's parent that will be in the toctree - (nodes.section, or a previously added addnodes.desc) - * Finding that parent in the TOC Tree based on it's ID - * Taking that element in the TOC Tree, - and finding it's parent that is a TOC Listing (nodes.bullet_list) - * Adding the new TOC element for our specific node as a child of that nodes.bullet_list - * This checks that bullet_list's last child, - and checks that it is also a nodes.bullet_list, - effectively nesting it under that element - """ - toc = app.env.tocs[docname] - for desc_node in doctree.traverse(addnodes.desc): - try: - ref_id = desc_node.children[0].attributes["ids"][0] - except (KeyError, IndexError): - # autodoc-style directives already add nodes to the toc. - continue - try: - # Python domain object - ref_text = desc_node[0].attributes["fullname"].split(".")[-1].split("(")[0] - except (KeyError, IndexError): - # TODO[eric]: Support other Domains and ways of accessing this data - # Use `astext` for other types of domain objects - ref_text = desc_node[0].astext().split(".")[-1].split("(")[0] - # This is the actual object that will exist in the TOC Tree - # Sections by default, and other Desc nodes that we've previously placed. - parent_node = _traverse_parent( - node=desc_node, objtypes=(addnodes.desc, nodes.section) - ) - - if parent_node: - toc_reference = _get_toc_reference(parent_node, toc, docname) - if toc_reference: - # Get the last child of our parent's bullet list, this is where "we" live. - toc_insertion_point = _traverse_parent( - toc_reference, nodes.bullet_list - )[-1] - # Ensure we're added another bullet list so that we nest inside the parent, - # not next to it - if len(toc_insertion_point) > 1 and isinstance( - toc_insertion_point[1], nodes.bullet_list - ): - to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text) - toc_insertion_point = toc_insertion_point[1] - else: - to_add = _build_toc_node( - docname, - anchor=ref_id, - text=ref_text, - bullet=True, - ) - toc_insertion_point.append(to_add) diff --git a/docs/changes/+toctree.removal.rst b/docs/changes/+toctree.removal.rst new file mode 100644 index 0000000..58a6c42 --- /dev/null +++ b/docs/changes/+toctree.removal.rst @@ -0,0 +1,2 @@ +Removed the option to have autoapi generate toctree entries for domain objects. +Dropped support for sphinx < 5.2.0. diff --git a/setup.cfg b/setup.cfg index cfddc64..f933f47 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,7 +37,7 @@ install_requires = astroid>=2.7 Jinja2 PyYAML - sphinx>=4.0 + sphinx>=5.2.0 unidecode [options.extras_require] diff --git a/tests/test_integration.py b/tests/test_integration.py index aaa91d7..6ccf3df 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -116,7 +116,9 @@ class TestTOCTree(LanguageIntegrationTests): """ Test that the example_function gets added to the TOC Tree """ - self._run_test("toctreeexample", "_build/text/index.txt", "* example_function") + self._run_test( + "toctreeexample", "_build/text/index.txt", '* "example_function()"' + ) class TestExtensionErrors: