Let Sphinx handle adding domain directives to the toctree (#374)

* Remove ability to add domains to the toctree

* Add towncrier news fragment
pull/375/head
Brandon Wiebe 1 year ago committed by GitHub
parent 007d9959a0
commit 6b16e02de4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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)

@ -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)

@ -0,0 +1,2 @@
Removed the option to have autoapi generate toctree entries for domain objects.
Dropped support for sphinx < 5.2.0.

@ -37,7 +37,7 @@ install_requires =
astroid>=2.7
Jinja2
PyYAML
sphinx>=4.0
sphinx>=5.2.0
unidecode
[options.extras_require]

@ -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:

Loading…
Cancel
Save