Let Sphinx handle adding domain directives to the toctree (#374)
* Remove ability to add domains to the toctree * Add towncrier news fragmentpull/375/head
parent
007d9959a0
commit
6b16e02de4
@ -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.
|
Loading…
Reference in New Issue