Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new options for moving the toctree #8847

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 66 additions & 2 deletions doc/usage/restructuredtext/directives.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ tables of contents. The ``toctree`` directive is the central element.

recipe/*

You can also give a "hidden" option to the directive, like this::
You can also give a ``hidden`` option to the directive, like this::

.. toctree::
:hidden:
Expand All @@ -185,7 +185,71 @@ tables of contents. The ``toctree`` directive is the central element.
doc_1
doc_2

All other toctree entries can then be eliminated by the "hidden" option.
All other toctree entries can then be eliminated by the ``hidden`` option.

When a ``toctree`` directive is encountered in a reST document, Sphinx will
add the listed entries as children of the current nesting level. This is
sometimes undesired. For example, placing a ``toctree`` at the end of a
document will make make the listed entries subsections of the last section of
the document. The default behavior can be changed with the ``raise_level``
option. Consider the following::

====
Main
====

-----
Sub 1
-----

.. toctree::
:raise_level: 1

sub_2
sub_3

The structure without the ``raise_level`` option would be:

* Main

* Sub 1

* Sub 2
* Sub 3

With ``:raise_level: 1`` it becomes:

* Main

* Sub 1
* Sub 2
* Sub 3

Sometimes, the nesting depth at the end of the document varies and
the ``raise_level`` option would need to be changed accordingly. In
these cases the ``toctree`` directive can be placed on an
appropriate level and moved using the ``index`` option, which
determines the (zero-based) index of the first entry of the
``toctree`` among its siblings. Negative indices are counted from
the end. For example, the following document produces the same
structure as the previous one::

====
Main
====

.. toctree::
:index: -1

sub_2
sub_3

-----
Sub 1
-----

The ``index`` option is usually used with ``hidden`` to put subsections
defined in other files after the sections in the current document.

In the end, all documents in the :term:`source directory` (or subdirectories)
must occur in some ``toctree`` directive; Sphinx will emit a warning if it
Expand Down
5 changes: 5 additions & 0 deletions sphinx/directives/other.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class TocTree(SphinxDirective):
'numbered': int_or_nothing,
'titlesonly': directives.flag,
'reversed': directives.flag,
'raise_level': int,
'index': int,
}

def run(self) -> List[Node]:
Expand All @@ -75,6 +77,9 @@ def run(self) -> List[Node]:
subnode['includehidden'] = 'includehidden' in self.options
subnode['numbered'] = self.options.get('numbered', 0)
subnode['titlesonly'] = 'titlesonly' in self.options
subnode['raise_level'] = self.options.get('raise_level', 0)
if 'index' in self.options:
subnode['index'] = self.options.get('index')
self.set_source_info(subnode)
wrappernode = nodes.compound(classes=['toctree-wrapper'])
wrappernode.append(subnode)
Expand Down
53 changes: 47 additions & 6 deletions sphinx/environment/adapters/toctree.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@
logger = logging.getLogger(__name__)


def parent_bullet_list(node, levels):
"""Scan up from node until *levels* bullet_list nodes are found.
Return the found node and the child through which the scan arrived at it.
"""
parent = node.parent
child = node
while parent and not isinstance(parent, nodes.bullet_list):
child = parent
parent = parent.parent
if levels == 0 or not parent:
return (parent, child)
else:
return parent_bullet_list(parent, levels - 1)


class TocTree:
def __init__(self, env: "BuildEnvironment") -> None:
self.env = env
Expand Down Expand Up @@ -201,15 +216,41 @@ def _entries_from_toctree(toctreenode: addnodes.toctree, parents: List[str],
toplevel.pop(1)
# resolve all sub-toctrees
for subtocnode in toc.traverse(addnodes.toctree):
if not (subtocnode.get('hidden', False) and
not includehidden):
i = subtocnode.parent.index(subtocnode) + 1
if not subtocnode.get('hidden', False) or includehidden:
raise_level = subtocnode.get('raise_level', 0)
if raise_level < 0:
logger.warning(__('negative raise_level option ignored'),
location=toctreenode)
raise_level = 0
parent, child = parent_bullet_list(subtocnode, raise_level)
if not parent:
logger.warning(__('toctree parent not found'),
location=toctreenode)
child = subtocnode
parent = subtocnode.parent

if 'index' in subtocnode:
insert_index = subtocnode.get('index')
if insert_index < 0:
insert_index = len(parent) + 1 + insert_index
if insert_index < 0 or insert_index > len(parent):
insert_index = 0
logger.warning(__('toctree index out of bounds'),
location=toctreenode)
else:
insert_index = parent.index(child) + 1

for entry in _entries_from_toctree(
subtocnode, [refdoc] + parents,
subtree=True):
subtocnode.parent.insert(i, entry)
i += 1
subtocnode.parent.remove(subtocnode)
parent.insert(insert_index, entry)
insert_index += 1

parent = subtocnode.parent
parent.remove(subtocnode)
if len(parent) == 0 and parent.parent:
parent.parent.remove(parent)

if separate:
entries.append(toc)
else:
Expand Down
3 changes: 3 additions & 0 deletions sphinx/ext/autosectionlabel.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None:
docname = app.env.docname
title = cast(nodes.title, node[0])
ref_name = getattr(title, 'rawsource', title.astext())
if app.config.autosectionlabel_word_separator != ' ':
ref_name = app.config.autosectionlabel_word_separator.join(ref_name.split())
if app.config.autosectionlabel_prefix_document:
name = nodes.fully_normalize_name(docname + ':' + ref_name)
else:
Expand All @@ -58,6 +60,7 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None:

def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('autosectionlabel_prefix_document', False, 'env')
app.add_config_value('autosectionlabel_word_separator', ' ', 'env')
app.add_config_value('autosectionlabel_maxdepth', None, 'env')
app.connect('doctree-read', register_sections_as_label)

Expand Down