Skip to content

Commit

Permalink
toctree: Use document nesting instead of domain nesting when adding d…
Browse files Browse the repository at this point in the history
…omain objects (#12367)

Co-authored-by: Adam Turner <[email protected]>
  • Loading branch information
jakobandersen and AA-Turner committed Jul 14, 2024
1 parent 159c267 commit e08f12f
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 14 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ Bugs fixed
Patch by James Addison and Will Lachance.
* #9634: Do not add a fallback language by stripping the country code.
Patch by Alvin Wong.
* #12352: Add domain objects to the table of contents
in the same order as defined in the document.
Previously, each domain used language-specific nesting rules,
which removed control from document authors.
Patch by Jakob Lykke Andersen and Adam Turner.

Testing
-------
Expand Down
36 changes: 23 additions & 13 deletions sphinx/environment/collectors/toctree.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ def build_toc(
) -> nodes.bullet_list | None:
# list of table of contents entries
entries: list[Element] = []
# cache of parents -> list item
memo_parents: dict[tuple[str, ...], nodes.list_item] = {}
for sectionnode in node:
# find all toctree nodes in this section and add them
# to the toc (just copying the toctree node which is then
Expand Down Expand Up @@ -103,6 +101,8 @@ def build_toc(
entries.append(onlynode)
# check within the section for other node types
elif isinstance(sectionnode, nodes.Element):
# cache of parent node -> list item
memo_parents: dict[nodes.Element, nodes.list_item] = {}
toctreenode: nodes.Node
for toctreenode in sectionnode.findall():
if isinstance(toctreenode, nodes.section):
Expand All @@ -114,6 +114,10 @@ def build_toc(
note_toctree(app.env, docname, toctreenode)
# add object signatures within a section to the ToC
elif isinstance(toctreenode, addnodes.desc):
# The desc has one or more nested desc_signature,
# and then a desc_content, which again may have desc nodes.
# Thus, desc is the one we can bubble up to through parents.
entry: nodes.list_item | None = None
for sig_node in toctreenode:
if not isinstance(sig_node, addnodes.desc_signature):
continue
Expand All @@ -136,22 +140,28 @@ def build_toc(
para = addnodes.compact_paragraph('', '', reference,
skip_section_number=True)
entry = nodes.list_item('', para)
*parents, _ = sig_node['_toc_parts']
parents = tuple(parents)

# Cache parents tuple
memo_parents[sig_node['_toc_parts']] = entry

# Nest children within parents
if parents and parents in memo_parents:
root_entry = memo_parents[parents]
# Find parent node
parent = sig_node.parent
while parent not in memo_parents and parent != sectionnode:
parent = parent.parent
# Note, it may both be the limit and in memo_parents,
# prefer memo_parents, so we get the nesting.
if parent in memo_parents:
root_entry = memo_parents[parent]
if isinstance(root_entry[-1], nodes.bullet_list):
root_entry[-1].append(entry)
else:
root_entry.append(nodes.bullet_list('', entry))
continue

entries.append(entry)
else:
assert parent == sectionnode
entries.append(entry)

# Save the latest desc_signature as the one we put sub entries in.
# If there are multiple signatures, then the latest is used.
if entry is not None:
# are there any desc nodes without desc_signature nodes?
memo_parents[toctreenode] = entry

if entries:
return nodes.bullet_list('', *entries)
Expand Down
23 changes: 23 additions & 0 deletions tests/roots/test-toctree-domain-objects/document_scoping.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Level 1
=======

.. py:class:: ClassLevel1a
ClassLevel1b
.. py:method:: f()
.. py:method:: ClassLevel1a.g()
.. py:method:: ClassLevel1b.g()
Level 2
-------

.. py:class:: ClassLevel2a
ClassLevel2b
.. py:method:: f()
.. py:method:: ClassLevel2a.g()
.. py:method:: ClassLevel2b.g()
1 change: 1 addition & 0 deletions tests/roots/test-toctree-domain-objects/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
:name: mastertoc

domains
document_scoping
37 changes: 36 additions & 1 deletion tests/test_environment/test_environment_toctree.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def test_domain_objects(app):

assert app.env.toc_num_entries['index'] == 0
assert app.env.toc_num_entries['domains'] == 9
assert app.env.toctree_includes['index'] == ['domains']
assert app.env.toctree_includes['index'] == ['domains', 'document_scoping']
assert 'index' in app.env.files_to_rebuild['domains']
assert app.env.glob_toctrees == set()
assert app.env.numbered_toctrees == {'index'}
Expand Down Expand Up @@ -161,6 +161,41 @@ def test_domain_objects(app):
[list_item, ([compact_paragraph, reference, literal, "HelloWorldPrinter.print()"])])


@pytest.mark.sphinx('dummy', testroot='toctree-domain-objects')
def test_domain_objects_document_scoping(app):
app.build()

# tocs
toctree = app.env.tocs['document_scoping']
assert_node(
toctree,
[bullet_list, list_item, (
compact_paragraph, # [0][0]
[bullet_list, ( # [0][1]
[list_item, compact_paragraph, reference, literal, 'ClassLevel1a'], # [0][1][0]
[list_item, ( # [0][1][1]
[compact_paragraph, reference, literal, 'ClassLevel1b'], # [0][1][1][0]
[bullet_list, list_item, compact_paragraph, reference, literal, 'ClassLevel1b.f()'], # [0][1][1][1][0]
)],
[list_item, compact_paragraph, reference, literal, 'ClassLevel1a.g()'], # [0][1][2]
[list_item, compact_paragraph, reference, literal, 'ClassLevel1b.g()'], # [0][1][3]
[list_item, ( # [0][1][4]
[compact_paragraph, reference, 'Level 2'], # [0][1][4][0]
[bullet_list, ( # [0][1][4][1]
[list_item, compact_paragraph, reference, literal, 'ClassLevel2a'], # [0][1][4][1][0]
[list_item, ( # [0][1][4][1][1]
[compact_paragraph, reference, literal, 'ClassLevel2b'], # [0][1][4][1][1][0]
[bullet_list, list_item, compact_paragraph, reference, literal, 'ClassLevel2b.f()'], # [0][1][4][1][1][1][0]
)],
[list_item, compact_paragraph, reference, literal, 'ClassLevel2a.g()'], # [0][1][4][1][2]
[list_item, compact_paragraph, reference, literal, 'ClassLevel2b.g()'], # [0][1][4][1][3]
)],
)],
)],
)],
)


@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
def test_document_toc(app):
Expand Down

0 comments on commit e08f12f

Please sign in to comment.