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 autosectionlabel_full_reference configuration variable #13076

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Deprecated
Features added
--------------

* #13076: Add the :confval:`autosectionlabel_full_reference`
configuration variable to create more precise labels.
Patch by Andrew Maguire

Bugs fixed
----------

Expand Down
26 changes: 26 additions & 0 deletions doc/usage/extensions/autosectionlabel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ default. The ``autosectionlabel_prefix_document`` configuration variable can be
used to make headings which appear multiple times but in different documents
unique.

Use the :confval:`autosectionlabel_full_reference` configuration variable
to guarantee that the generated references are unique across a document
with similar section names at different levels.


Configuration
-------------
Expand All @@ -50,6 +54,28 @@ Configuration
only for top level sections, and deeper sections are not labeled. It
defaults to ``None`` (i.e. all sections are labeled).

.. confval:: autosectionlabel_full_reference
:type: :code-py:`bool`
:default: :code-py:`False`

.. versionadded:: 8.2

True to make each section label include all the parent sections separated by
a colon. For instance, if the following appears in document ``index.rst``:

.. code-block:: rst

Title
=====

Section
-------

Sub Section
~~~~~~~~~~~

The reference of the third level section ("Sub Section") will be
``index:Title:Section:Sub Section``.

Debugging
---------
Expand Down
49 changes: 42 additions & 7 deletions sphinx/ext/autosectionlabel.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sphinx
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.nodes import clean_astext
from sphinx.util.nodes import clean_astext, make_id, traverse_parent

if TYPE_CHECKING:
from docutils.nodes import Node
Expand All @@ -29,24 +29,58 @@ def get_node_depth(node: Node) -> int:
return i


def register_sections_as_label(app: Sphinx, document: Node) -> None:
def _get_all_node_parent_section_titles(node: Node) -> list[str]:
parents = []
for pnode in traverse_parent(node.parent, nodes.section):
title = cast(nodes.title, pnode[0])
ref_name = getattr(title, 'rawsource', title.astext())
parents.append(ref_name)

return parents


def register_sections_as_label(app: Sphinx, doctree: nodes.document) -> None:
domain = app.env.domains.standard_domain
for node in document.findall(nodes.section):
id_prefix = None

settings = doctree.settings
for node in doctree.findall(nodes.section):
if (app.config.autosectionlabel_maxdepth and
get_node_depth(node) >= app.config.autosectionlabel_maxdepth):
continue

labelid = node['ids'][0]
docname = app.env.docname
title = cast(nodes.title, node[0])
ref_name = getattr(title, 'rawsource', title.astext())

if app.config.autosectionlabel_prefix_document:
name = nodes.fully_normalize_name(docname + ':' + ref_name)
if app.config.autosectionlabel_full_reference:
id_array = _get_all_node_parent_section_titles(node)
id_array = [docname, *reversed(id_array), ref_name]
# replace id_prefix temporarily
id_prefix = settings.id_prefix
settings.id_prefix = '.'.join(id_array)

labelid = make_id(app.env, doctree, '', '.'.join(id_array))
doctree.ids[labelid] = labelid
name = nodes.fully_normalize_name(labelid.replace('.', ':'))

# restore id_prefix
settings.id_prefix = id_prefix

# Add labelid as another reference id
# Note, cannot replace as this breaks TOC and sectnum functionality.
node['ids'].append(labelid)
else:
name = nodes.fully_normalize_name(f'{docname}:{ref_name}')
else:
name = nodes.fully_normalize_name(ref_name)

sectname = clean_astext(title)

logger.debug(__('section "%s" gets labeled as "%s"'),
ref_name, name,
logger.debug(__('section "%s" is: labeled "%s", id "%s", prefix "%s"'),
ref_name, name, labelid, id_prefix,
location=node, type='autosectionlabel', subtype=docname)
if name in domain.labels:
logger.warning(__('duplicate label %s, other instance in %s'),
Expand All @@ -58,8 +92,9 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None:


def setup(app: Sphinx) -> ExtensionMetadata:
app.add_config_value('autosectionlabel_prefix_document', False, 'env')
app.add_config_value('autosectionlabel_prefix_document', False, 'env', bool)
app.add_config_value('autosectionlabel_maxdepth', None, 'env')
app.add_config_value('autosectionlabel_full_reference', False, 'env', bool)
app.connect('doctree-read', register_sections_as_label)

return {
Expand Down
14 changes: 13 additions & 1 deletion sphinx/util/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,19 @@ def make_id(
node_id = None # fallback to None

while node_id is None or node_id in document.ids:
node_id = idformat % env.new_serialno(prefix)
node_id = _make_id(idformat % env.new_serialno(prefix))

logger.debug(
__(
'NODE "%s" gets term "%s" with ID format "%s" and prefix is "%s" and ids is "s"'
),
node_id,
term,
idformat,
prefix, # document.ids,
location=document,
type='make_id',
)

return node_id

Expand Down
7 changes: 6 additions & 1 deletion sphinx/writers/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,9 @@ def astext(self) -> str:
return self.render('latex.tex.jinja', self.elements)

def hypertarget(self, id: str, withdoc: bool = True, anchor: bool = True) -> str:
if withdoc:
if getattr(self.config, 'autosectionlabel_full_reference', False):
pass
elif withdoc:
id = self.curfilestack[-1] + ':' + id
escaped_id = self.idescape(id)
return (r'\phantomsection' if anchor else '') + r'\label{%s}' % escaped_id
Expand Down Expand Up @@ -1971,9 +1973,12 @@ def visit_reference(self, node: Element) -> None:
if hashindex == -1:
# reference to the document
id = uri[1:] + '::doc'
elif getattr(self.config, 'autosectionlabel_full_reference', False):
id = uri[hashindex + 1 :]
else:
# reference to a label
id = uri[1:].replace('#', ':')

self.body.append(self.hyperlink(id))
if (
len(node)
Expand Down
3 changes: 2 additions & 1 deletion sphinx/writers/texinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,8 @@ def get_short_id(self, id: str) -> str:
def add_anchor(self, id: str, node: Node) -> None:
if id.startswith('index-'):
return
id = self.curfilestack[-1] + ':' + id
if not getattr(self.config, 'autosectionlabel_full_reference', False):
id = self.curfilestack[-1] + ':' + id
eid = self.escape_id(id)
sid = self.get_short_id(id)
for id in (eid, sid):
Expand Down
6 changes: 6 additions & 0 deletions tests/roots/test-ext-autosectionlabel-full-reference/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extensions = ['sphinx.ext.autosectionlabel']
autosectionlabel_prefix_document = True
autosectionlabel_full_reference = True
latex_documents = [
('index', 'test.tex', '', 'Sphinx', 'report')
]
49 changes: 49 additions & 0 deletions tests/roots/test-ext-autosectionlabel-full-reference/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Introduction of Sphinx
======================


Directives
----------


Installation
============

.. include:: windows.rst

For UNIX users
--------------

Linux
^^^^^

Command1
''''''''

FreeBSD
^^^^^^^

2nd Command
'''''''''''

This one's got an apostrophe
----------------------------


References
==========

* :ref:`index:Introduction-of-Sphinx`
* :ref:`index:Installation`
* :ref:`index:Installation:For-Windows-users`
* :ref:`index:Installation:For-Windows-users:Windows`
* :ref:`index:Installation:For-Windows-users:Windows:Command`
* :ref:`index:Installation:For-Windows-users:Windows:Command0`
* :ref:`index:Installation:For-Windows-users:Windows:Command1`
* :ref:`index:Installation:For-UNIX-users`
* :ref:`index:Installation:For-UNIX-users:Linux`
* :ref:`index:Installation:For-UNIX-users:Linux:Command1`
* :ref:`index:Installation:For-UNIX-users:FreeBSD`
* :ref:`index:Installation:For-UNIX-users:FreeBSD:2nd-Command`
* :ref:`index:Installation:This-one-s-got-an-apostrophe`

27 changes: 27 additions & 0 deletions tests/roots/test-ext-autosectionlabel-full-reference/windows.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
For Windows users
-----------------

Windows
^^^^^^^

Command
'''''''

Command
'''''''

This has the same name as the section before but a unique label id is required.

Command
'''''''

This has the same name as the two sections before but a unique label id is required.

Local References
^^^^^^^^^^^^^^^^

* :ref:`windows:For-Windows-users`
* :ref:`windows:For-Windows-users:Windows`
* :ref:`windows:For-Windows-users:Windows:Command`
* :ref:`windows:For-Windows-users:Windows:Command0`
* :ref:`windows:For-Windows-users:Windows:Command1`
Loading
Loading