From a767c8bb5eadfe948546d619f61ef8825f34fed1 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Mon, 28 Oct 2024 09:33:31 +1300 Subject: [PATCH 01/13] Add autosectionlabel_full_reference configuration variable --- doc/usage/extensions/autosectionlabel.rst | 22 +++++- sphinx/ext/autosectionlabel.py | 22 +++++- .../conf.py | 3 + .../index.rst | 48 ++++++++++++ .../test_ext_autosectionlabel.py | 75 +++++++++++++++++++ 5 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 tests/roots/test-ext-autosectionlabel-full-reference/conf.py create mode 100644 tests/roots/test-ext-autosectionlabel-full-reference/index.rst diff --git a/doc/usage/extensions/autosectionlabel.rst b/doc/usage/extensions/autosectionlabel.rst index 1e9e1dba722..a36dd2e0b41 100644 --- a/doc/usage/extensions/autosectionlabel.rst +++ b/doc/usage/extensions/autosectionlabel.rst @@ -25,7 +25,9 @@ Internally, this extension generates the labels for each section. If same section names are used in whole of document, any one is used for a target by default. The ``autosectionlabel_prefix_document`` configuration variable can be used to make headings which appear multiple times but in different documents -unique. +unique. Also the ``autosectionlabel_full_reference`` configuration variable can +help ensure the generated refereences are unique across a document with similar +section names at different levels. Configuration @@ -50,6 +52,24 @@ 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` + + True to make each section label include all the parent sections separated by + a colon. For example:: + + Title + ===== + + Section + ------- + + Sub Section + ~~~~~~~~~~~ + + Then the reference of the third level section is + ``index:Title:Section:Sub Section``. Debugging --------- diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index 63885e7039f..02e2e401460 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -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 if TYPE_CHECKING: from docutils.nodes import Node @@ -28,6 +28,15 @@ def get_node_depth(node: Node) -> int: i += 1 return i +def get_node_parents(node: Node) -> list[str]: + parents = [] + cur_node = node + while cur_node.parent != node.document: + cur_node = cur_node.parent + title = cast(nodes.title, cur_node[0]) + ref_name = getattr(title, 'rawsource', title.astext()) + parents.append(ref_name) + return parents def register_sections_as_label(app: Sphinx, document: Node) -> None: domain = app.env.domains.standard_domain @@ -40,7 +49,15 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None: 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_node_parents(node) + id_array.append(docname) + id_array.reverse() + id_array.append(ref_name) + name = nodes.fully_normalize_name(':'.join(id_array)) + labelid = make_id(app.env, document, '', '.'.join(id_array)) + else: + name = nodes.fully_normalize_name(docname + ':' + ref_name) else: name = nodes.fully_normalize_name(ref_name) sectname = clean_astext(title) @@ -60,6 +77,7 @@ 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_maxdepth', None, 'env') + app.add_config_value('autosectionlabel_full_reference', False, 'env') app.connect('doctree-read', register_sections_as_label) return { diff --git a/tests/roots/test-ext-autosectionlabel-full-reference/conf.py b/tests/roots/test-ext-autosectionlabel-full-reference/conf.py new file mode 100644 index 00000000000..47020e2b4c6 --- /dev/null +++ b/tests/roots/test-ext-autosectionlabel-full-reference/conf.py @@ -0,0 +1,3 @@ +extensions = ['sphinx.ext.autosectionlabel'] +autosectionlabel_prefix_document = True +autosectionlabel_full_reference = True diff --git a/tests/roots/test-ext-autosectionlabel-full-reference/index.rst b/tests/roots/test-ext-autosectionlabel-full-reference/index.rst new file mode 100644 index 00000000000..dca01bf9e8b --- /dev/null +++ b/tests/roots/test-ext-autosectionlabel-full-reference/index.rst @@ -0,0 +1,48 @@ +Introduction of Sphinx +====================== + +Installation +============ + +For Windows users +----------------- + +Windows +^^^^^^^ + +Command +''''''' + +For UNIX users +-------------- + +Linux +^^^^^ + +Command +''''''' + +FreeBSD +^^^^^^^ + +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 UNIX users` +* :ref:`index:Installation:For UNIX users:Linux` +* :ref:`index:Installation:For UNIX users:Linux:Command` +* :ref:`index:Installation:For UNIX users:FreeBSD` +* :ref:`index:Installation:For UNIX users:FreeBSD:Command` +* :ref:`index:Installation:This one's got an apostrophe` diff --git a/tests/test_extensions/test_ext_autosectionlabel.py b/tests/test_extensions/test_ext_autosectionlabel.py index 2133f64bfaf..3f361cfbdf6 100644 --- a/tests/test_extensions/test_ext_autosectionlabel.py +++ b/tests/test_extensions/test_ext_autosectionlabel.py @@ -98,3 +98,78 @@ def test_autosectionlabel_maxdepth(app): assert re.search(html, content, re.DOTALL) assert "WARNING: undefined label: 'linux'" in app.warning.getvalue() + + +@pytest.mark.sphinx('html', testroot='ext-autosectionlabel-full-reference') +def test_autosectionlabel_full_reference(app): + app.build(force_all=True) + + content = (app.outdir / 'index.html').read_text(encoding='utf8') + html = ( + '
  • ' + 'Introduction of Sphinx

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'Installation

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'For Windows users

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'Windows

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'Command

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'For UNIX users

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'Linux

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'Command

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'FreeBSD

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + '
  • ' + 'Command

  • ' + ) + assert re.search(html, content, re.DOTALL) + + # for smart_quotes (refs: #4027) + html = ( + '
  • ' + 'This one’s got an apostrophe' + '

  • ' + ) + assert re.search(html, content, re.DOTALL) From 0efcc456c79768a4b182d881628e0a9ca8baac0c Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Mon, 28 Oct 2024 13:07:51 +1300 Subject: [PATCH 02/13] fix lint errors --- sphinx/ext/autosectionlabel.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index 02e2e401460..c5f9ca376be 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -28,6 +28,7 @@ def get_node_depth(node: Node) -> int: i += 1 return i + def get_node_parents(node: Node) -> list[str]: parents = [] cur_node = node @@ -38,6 +39,7 @@ def get_node_parents(node: Node) -> list[str]: parents.append(ref_name) return parents + def register_sections_as_label(app: Sphinx, document: Node) -> None: domain = app.env.domains.standard_domain for node in document.findall(nodes.section): @@ -55,7 +57,7 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None: id_array.reverse() id_array.append(ref_name) name = nodes.fully_normalize_name(':'.join(id_array)) - labelid = make_id(app.env, document, '', '.'.join(id_array)) + labelid = make_id(app.env, node.document, '', '.'.join(id_array)) else: name = nodes.fully_normalize_name(docname + ':' + ref_name) else: From 8eace38f298c9e437190fa4f9ed9cc2ccc1f339a Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Mon, 4 Nov 2024 15:05:37 +1300 Subject: [PATCH 03/13] Added autosectionlabel_full_reference to CHANGES.rst In usage, updated as suggested in review Added versionadded 8.2 for the new feature Walked node tree looking only for nodes.section type. Prefixed internally used functions with _. Added bool type to add_config_value calls Used Python f'' string for efficiency Overrides document.settings.id_prefix so that suitable references are created in case of any duplicate section hierarchy. Tests for these are created too. The autosectionlabel_full_reference tests now include another file to check multiple file references. When using autosectionlabel_full_reference in texinfo, the filename is not prefixed in references. Added texinfo tests. When using autosectionlabel_full_reference in latex, the filename is not prefixed in references. Added latex tests All new tests pass All type-checks pass --- CHANGES.rst | 2 + doc/usage/extensions/autosectionlabel.rst | 16 +- sphinx/ext/autosectionlabel.py | 50 +-- sphinx/util/nodes.py | 6 +- sphinx/writers/latex.py | 7 +- sphinx/writers/texinfo.py | 3 +- .../conf.py | 3 + .../index.rst | 45 +-- .../windows.rst | 27 ++ .../test_ext_autosectionlabel.py | 285 +++++++++++++++++- 10 files changed, 383 insertions(+), 61 deletions(-) create mode 100644 tests/roots/test-ext-autosectionlabel-full-reference/windows.rst diff --git a/CHANGES.rst b/CHANGES.rst index b47f417e9a1..097fcd9bb92 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,8 @@ Deprecated Features added -------------- +* #13076: Add autosectionlabel_full_reference configuration variable. + Bugs fixed ---------- diff --git a/doc/usage/extensions/autosectionlabel.rst b/doc/usage/extensions/autosectionlabel.rst index a36dd2e0b41..282cc9e85ae 100644 --- a/doc/usage/extensions/autosectionlabel.rst +++ b/doc/usage/extensions/autosectionlabel.rst @@ -25,9 +25,11 @@ Internally, this extension generates the labels for each section. If same section names are used in whole of document, any one is used for a target by default. The ``autosectionlabel_prefix_document`` configuration variable can be used to make headings which appear multiple times but in different documents -unique. Also the ``autosectionlabel_full_reference`` configuration variable can -help ensure the generated refereences are unique across a document with similar -section names at different levels. +unique. + +Use the :conf:`autosectionlabel_full_reference` configuration variable +to guarantee that the generated references are unique across a document +with similar section names at different levels. Configuration @@ -56,8 +58,12 @@ Configuration :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 example:: + a colon. For instance, if the following appears in document ``index.rst``: + + .. code-block:: rst Title ===== @@ -68,7 +74,7 @@ Configuration Sub Section ~~~~~~~~~~~ - Then the reference of the third level section is + The reference of the third level section ("Sub Section") will be ``index:Title:Section:Sub Section``. Debugging diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index c5f9ca376be..8194c58759f 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -9,7 +9,7 @@ import sphinx from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.nodes import clean_astext, make_id +from sphinx.util.nodes import clean_astext, make_id, traverse_parent if TYPE_CHECKING: from docutils.nodes import Node @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) -def get_node_depth(node: Node) -> int: +def _get_node_depth(node: Node) -> int: i = 0 cur_node = node while cur_node.parent != node.document: @@ -29,43 +29,57 @@ def get_node_depth(node: Node) -> int: return i -def get_node_parents(node: Node) -> list[str]: +def _find_node_parents(node: Node) -> list[str]: parents = [] - cur_node = node - while cur_node.parent != node.document: - cur_node = cur_node.parent - title = cast(nodes.title, cur_node[0]) + 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, document: Node) -> None: +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): + _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: if app.config.autosectionlabel_full_reference: - id_array = get_node_parents(node) + id_array = _find_node_parents(node) id_array.append(docname) id_array.reverse() id_array.append(ref_name) - name = nodes.fully_normalize_name(':'.join(id_array)) - labelid = make_id(app.env, node.document, '', '.'.join(id_array)) + # replace id_prefix temporarily + id_prefix = settings.id_prefix + settings.id_prefix = '.'.join(id_array) + + labelid = make_id(app.env, doctree, '', '.'.join(id_array)) + node['ids'][0] = labelid + doctree.ids[labelid] = labelid + name = nodes.fully_normalize_name(labelid.replace('.', ':')) + + # restore id_prefix + settings.id_prefix = id_prefix else: - name = nodes.fully_normalize_name(docname + ':' + ref_name) + 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" gets labeled as "%s" with id as "%s" and prefix is "%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'), @@ -77,9 +91,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') + app.add_config_value('autosectionlabel_full_reference', False, 'env', bool) app.connect('doctree-read', register_sections_as_label) return { diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 7f06ae194fc..d98d40ca500 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -597,7 +597,11 @@ 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 diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c98135efa7f..ac5c39b56a0 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -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 self.config.autosectionlabel_full_reference: + pass + elif withdoc: id = self.curfilestack[-1] + ':' + id escaped_id = self.idescape(id) return (r'\phantomsection' if anchor else '') + r'\label{%s}' % escaped_id @@ -1971,9 +1973,12 @@ def visit_reference(self, node: Element) -> None: if hashindex == -1: # reference to the document id = uri[1:] + '::doc' + elif self.config.autosectionlabel_full_reference: + id = uri[hashindex + 1:] else: # reference to a label id = uri[1:].replace('#', ':') + self.body.append(self.hyperlink(id)) if ( len(node) diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 997561b16fd..aa719be638e 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -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 self.config.autosectionlabel_full_reference: + id = self.curfilestack[-1] + ':' + id eid = self.escape_id(id) sid = self.get_short_id(id) for id in (eid, sid): diff --git a/tests/roots/test-ext-autosectionlabel-full-reference/conf.py b/tests/roots/test-ext-autosectionlabel-full-reference/conf.py index 47020e2b4c6..365ca3a35e7 100644 --- a/tests/roots/test-ext-autosectionlabel-full-reference/conf.py +++ b/tests/roots/test-ext-autosectionlabel-full-reference/conf.py @@ -1,3 +1,6 @@ extensions = ['sphinx.ext.autosectionlabel'] autosectionlabel_prefix_document = True autosectionlabel_full_reference = True +latex_documents = [ + ('index', 'test.tex', '', 'Sphinx', 'report') +] diff --git a/tests/roots/test-ext-autosectionlabel-full-reference/index.rst b/tests/roots/test-ext-autosectionlabel-full-reference/index.rst index dca01bf9e8b..c4e4e4c992b 100644 --- a/tests/roots/test-ext-autosectionlabel-full-reference/index.rst +++ b/tests/roots/test-ext-autosectionlabel-full-reference/index.rst @@ -1,17 +1,15 @@ Introduction of Sphinx ====================== -Installation -============ -For Windows users ------------------ +Directives +---------- -Windows -^^^^^^^ -Command -''''''' +Installation +============ + +.. include:: windows.rst For UNIX users -------------- @@ -19,14 +17,14 @@ For UNIX users Linux ^^^^^ -Command -''''''' +Command1 +'''''''' FreeBSD ^^^^^^^ -Command -''''''' +2nd Command +''''''''''' This one's got an apostrophe ---------------------------- @@ -35,14 +33,17 @@ This one's got an apostrophe References ========== -* :ref:`index:Introduction of Sphinx` +* :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 UNIX users` -* :ref:`index:Installation:For UNIX users:Linux` -* :ref:`index:Installation:For UNIX users:Linux:Command` -* :ref:`index:Installation:For UNIX users:FreeBSD` -* :ref:`index:Installation:For UNIX users:FreeBSD:Command` -* :ref:`index:Installation:This one's got an apostrophe` +* :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` + diff --git a/tests/roots/test-ext-autosectionlabel-full-reference/windows.rst b/tests/roots/test-ext-autosectionlabel-full-reference/windows.rst new file mode 100644 index 00000000000..70286c32104 --- /dev/null +++ b/tests/roots/test-ext-autosectionlabel-full-reference/windows.rst @@ -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` diff --git a/tests/test_extensions/test_ext_autosectionlabel.py b/tests/test_extensions/test_ext_autosectionlabel.py index 3f361cfbdf6..ccf95e44f9e 100644 --- a/tests/test_extensions/test_ext_autosectionlabel.py +++ b/tests/test_extensions/test_ext_autosectionlabel.py @@ -4,6 +4,7 @@ import pytest +from pathlib import Path @pytest.mark.sphinx('html', testroot='ext-autosectionlabel') def test_autosectionlabel_html(app, skipped_labels=False): @@ -56,6 +57,31 @@ def test_autosectionlabel_html(app, skipped_labels=False): assert re.search(html, content, re.DOTALL) +@pytest.mark.sphinx('texinfo', testroot='ext-autosectionlabel') +def test_autosectionlabel_texinfo(app): + app.build(force_all=True) + + content = (app.outdir / 'projectnamenotset.texi').read_text(encoding='utf8') + + texinfo = ( + '@node Installation,References,Introduce of Sphinx,Top' + '\n' + '@anchor{index installation}@anchor{3}' + '\n' + '@chapter Installation' + ) + assert texinfo in content + + texinfo = ( + '@node For Windows users,For UNIX users,,Installation' + '\n' + '@anchor{index for-windows-users}@anchor{4}' + '\n' + '@section For Windows users' + ) + assert texinfo in content + + # Reuse test definition from above, just change the test root directory @pytest.mark.sphinx('html', testroot='ext-autosectionlabel-prefix-document') def test_autosectionlabel_prefix_document_html(app): @@ -104,72 +130,305 @@ def test_autosectionlabel_maxdepth(app): def test_autosectionlabel_full_reference(app): app.build(force_all=True) - content = (app.outdir / 'index.html').read_text(encoding='utf8') + _autosectionlabel_full_reference_html_index(app.outdir / 'index.html') + _autosectionlabel_full_reference_html_windows(app.outdir / 'windows.html') + + +def _autosectionlabel_full_reference_html_index(file: Path) -> None: + + content = file.read_text(encoding='utf8') + html = ( - '
  • ' + 'Introduction of Sphinx' + '.*' + '

  • ' 'Introduction of Sphinx

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' + 'Installation' + '.*' + '

  • ' 'Installation

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' + 'For Windows users' + '.*' + '

  • ' 'For Windows users

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' + 'Windows' + '.*' + '

  • ' 'Windows

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' + 'Command' + '.*' + '

  • ' 'Command

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' + 'Command' + '.*' + '

  • ' + 'Command

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + 'Command' + '.*' + '
  • ' + 'Command

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + 'For UNIX users' + '.*' + '
  • ' 'For UNIX users

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' + 'Linux' + '.*' + '

  • ' 'Linux

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' - 'Command

  • ' + 'Command1' + '.*' + '
  • ' + 'Command1

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' + 'FreeBSD' + '.*' + '

  • ' 'FreeBSD

  • ' ) assert re.search(html, content, re.DOTALL) html = ( - '
  • ' - 'Command

  • ' + '2nd Command' + '.*' + '
  • ' + '2nd Command

  • ' ) assert re.search(html, content, re.DOTALL) # for smart_quotes (refs: #4027) html = ( + 'This one’s got an apostrophe' + '.*' '
  • ' 'This one’s got an apostrophe' '

  • ' ) assert re.search(html, content, re.DOTALL) + + +def _autosectionlabel_full_reference_html_windows(file: Path) -> None: + + content = file.read_text(encoding='utf8') + + html = ( + 'For Windows users' + '.*' + '
  • ' + 'For Windows users

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + 'Windows' + '.*' + '
  • ' + 'Windows

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + 'Command' + '.*' + '
  • ' + 'Command

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + 'Command' + '.*' + '
  • ' + 'Command

  • ' + ) + assert re.search(html, content, re.DOTALL) + + html = ( + 'Command' + '.*' + '
  • ' + 'Command

  • ' + ) + assert re.search(html, content, re.DOTALL) + + +@pytest.mark.sphinx('latex', testroot='ext-autosectionlabel-full-reference') +def test_autosectionlabel_full_reference_latex(app): + app.build(force_all=True) + + content = (app.outdir / 'test.tex').read_text(encoding='utf8') + content = content.replace('\\', '.').replace('[', '.').replace(']','.') + + latex = ( + 'chapter{Installation}' + '.' + '.label{.detokenize{index.Installation}}' + '.*' + '{.hyperref..detokenize{index.Installation}.{.sphinxcrossref{.DUrole{std}{.DUrole{std-ref}{Installation}}}}}' + ) + assert re.search(latex, content, re.DOTALL) + + latex = ( + 'subsubsection{Command}' + '.' + '.label{.detokenize{index.Installation.For-Windows-users.Windows.Command}}' + '.*' + '{.hyperref..detokenize{index.Installation.For-Windows-users.Windows.Command}.' + '{.sphinxcrossref{.DUrole{std}{.DUrole{std-ref}{Command}}}}}' + ) + assert re.search(latex, content, re.DOTALL) + + latex = ( + 'subsubsection{Command}' + '.' + '.label{.detokenize{index.Installation.For-Windows-users.Windows.Command0}}' + '.*' + '{.hyperref..detokenize{index.Installation.For-Windows-users.Windows.Command0}.' + '{.sphinxcrossref{.DUrole{std}{.DUrole{std-ref}{Command}}}}}' + ) + assert re.search(latex, content, re.DOTALL) + + latex = ( + 'subsubsection{Command}' + '.' + '.label{.detokenize{index.Installation.For-Windows-users.Windows.Command1}}' + '.*' + '{.hyperref..detokenize{index.Installation.For-Windows-users.Windows.Command1}.' + '{.sphinxcrossref{.DUrole{std}{.DUrole{std-ref}{Command}}}}}' + ) + assert re.search(latex, content, re.DOTALL) + + +@pytest.mark.sphinx('texinfo', testroot='ext-autosectionlabel-full-reference') +def test_autosectionlabel_full_reference_texinfo(app): + app.build(force_all=True) + + content = (app.outdir / 'projectnamenotset.texi').read_text(encoding='utf8') + + texinfo = ( + '@node Installation,References,Directives,Top' + '\n' + '@anchor{index Installation}@anchor{3}' + '\n' + '@unnumbered Installation' + ) + assert texinfo in content + + texinfo = ( + '@node Command,Command<2>,,Windows' + '\n' + '@anchor{index Installation For-Windows-users Windows Command}@anchor{6}' + '\n' + '@subsection Command' + ) + assert texinfo in content + + texinfo = ( + '@node Command<2>,Command<3>,Command,Windows' + '\n' + '@anchor{index Installation For-Windows-users Windows Command0}@anchor{7}' + '\n' + '@subsection Command' + ) + assert texinfo in content + + texinfo = ( + '@node Command<3>,,Command<2>,Windows' + '\n' + '@anchor{index Installation For-Windows-users Windows Command1}@anchor{8}' + '\n' + '@subsection Command' + ) + assert texinfo in content From 40d481a7a7ace13d3ce3b91223257df80684d322 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Wed, 6 Nov 2024 17:40:25 +1300 Subject: [PATCH 04/13] Fixes to pass ruff format and flake8 style-lint --- sphinx/ext/autosectionlabel.py | 2 +- sphinx/util/nodes.py | 14 +++++++++++--- sphinx/writers/latex.py | 2 +- tests/test_extensions/test_ext_autosectionlabel.py | 8 +++----- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index 8194c58759f..400ec635fc9 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -78,7 +78,7 @@ def register_sections_as_label(app: Sphinx, doctree: nodes.document) -> None: sectname = clean_astext(title) - logger.debug(__('section "%s" gets labeled as "%s" with id as "%s" and prefix is "%s"'), + 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: diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index d98d40ca500..c2642b6a10c 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -599,9 +599,17 @@ def make_id( while node_id is None or node_id in document.ids: 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') + 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 diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index ac5c39b56a0..abdd83bb91f 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1974,7 +1974,7 @@ def visit_reference(self, node: Element) -> None: # reference to the document id = uri[1:] + '::doc' elif self.config.autosectionlabel_full_reference: - id = uri[hashindex + 1:] + id = uri[hashindex + 1 :] else: # reference to a label id = uri[1:].replace('#', ':') diff --git a/tests/test_extensions/test_ext_autosectionlabel.py b/tests/test_extensions/test_ext_autosectionlabel.py index ccf95e44f9e..f18726280da 100644 --- a/tests/test_extensions/test_ext_autosectionlabel.py +++ b/tests/test_extensions/test_ext_autosectionlabel.py @@ -1,10 +1,10 @@ """Test sphinx.ext.autosectionlabel extension.""" import re +from pathlib import Path import pytest -from pathlib import Path @pytest.mark.sphinx('html', testroot='ext-autosectionlabel') def test_autosectionlabel_html(app, skipped_labels=False): @@ -135,7 +135,6 @@ def test_autosectionlabel_full_reference(app): def _autosectionlabel_full_reference_html_index(file: Path) -> None: - content = file.read_text(encoding='utf8') html = ( @@ -143,7 +142,7 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: ' href="#index.Introduction-of-Sphinx"' ' title="Link to this heading">' '.*' - '
  • ' 'Introduction of Sphinx

  • ' ) @@ -285,7 +284,6 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: def _autosectionlabel_full_reference_html_windows(file: Path) -> None: - content = file.read_text(encoding='utf8') html = ( @@ -349,7 +347,7 @@ def test_autosectionlabel_full_reference_latex(app): app.build(force_all=True) content = (app.outdir / 'test.tex').read_text(encoding='utf8') - content = content.replace('\\', '.').replace('[', '.').replace(']','.') + content = content.replace('\\', '.').replace('[', '.').replace(']', '.') latex = ( 'chapter{Installation}' From 7ba2c93608411eea9d68f32b9e8cb55b7d673dcf Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Thu, 7 Nov 2024 10:37:58 +1300 Subject: [PATCH 05/13] Use :confval: to refer to autosectionlabel_full_reference in documentation Undo _get_node_depth rename Change _find_node_parents to _get_all_node_parent_section_titles to describe more fully what the function does and _ prefix is just for internal use. --- CHANGES.rst | 4 +++- doc/usage/extensions/autosectionlabel.rst | 2 +- sphinx/ext/autosectionlabel.py | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 097fcd9bb92..967dc112b57 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,7 +15,9 @@ Deprecated Features added -------------- -* #13076: Add autosectionlabel_full_reference configuration variable. +* #13076: Add the :confval:`autosectionlabel_full_reference` + configuration variable to create more precise labels. + Patch by Andrew Maguire Bugs fixed ---------- diff --git a/doc/usage/extensions/autosectionlabel.rst b/doc/usage/extensions/autosectionlabel.rst index 282cc9e85ae..2ad9201a9a2 100644 --- a/doc/usage/extensions/autosectionlabel.rst +++ b/doc/usage/extensions/autosectionlabel.rst @@ -27,7 +27,7 @@ default. The ``autosectionlabel_prefix_document`` configuration variable can be used to make headings which appear multiple times but in different documents unique. -Use the :conf:`autosectionlabel_full_reference` configuration variable +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. diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index 400ec635fc9..e6d32bc3596 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) -def _get_node_depth(node: Node) -> int: +def get_node_depth(node: Node) -> int: i = 0 cur_node = node while cur_node.parent != node.document: @@ -29,7 +29,7 @@ def _get_node_depth(node: Node) -> int: return i -def _find_node_parents(node: Node) -> list[str]: +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]) @@ -46,7 +46,7 @@ def register_sections_as_label(app: Sphinx, doctree: nodes.document) -> 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): + get_node_depth(node) >= app.config.autosectionlabel_maxdepth): continue labelid = node['ids'][0] @@ -56,7 +56,7 @@ def register_sections_as_label(app: Sphinx, doctree: nodes.document) -> None: if app.config.autosectionlabel_prefix_document: if app.config.autosectionlabel_full_reference: - id_array = _find_node_parents(node) + id_array = _get_all_node_parent_section_titles(node) id_array.append(docname) id_array.reverse() id_array.append(ref_name) From 9072d6213b47b3a5e11f669a512ca2d64616a414 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Mon, 28 Oct 2024 10:04:41 +1300 Subject: [PATCH 06/13] python 3.10 --- pyproject.toml | 2 +- sphinx/builders/linkcheck.py | 5 ++++- sphinx/config.py | 2 +- sphinx/theming.py | 5 ++++- sphinx/util/i18n.py | 5 ++++- sphinx/util/typing.py | 3 ++- tests/conftest.py | 2 ++ 7 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2e97d5eee54..ce48937eed3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ urls.Download = "https://pypi.org/project/Sphinx/" urls.Homepage = "https://www.sphinx-doc.org/" urls."Issue tracker" = "https://github.com/sphinx-doc/sphinx/issues" license.text = "BSD-2-Clause" -requires-python = ">=3.11" +requires-python = ">=3.10" # Classifiers list: https://pypi.org/classifiers/ classifiers = [ diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index fcf994e8e03..3ba5a06ddd1 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -7,7 +7,10 @@ import re import socket import time -from enum import StrEnum + +# from enum import StrEnum +from strenum import StrEnum + from html.parser import HTMLParser from os import path from queue import PriorityQueue, Queue diff --git a/sphinx/config.py b/sphinx/config.py index 8700ed30054..ccf47306837 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -6,7 +6,7 @@ import traceback import types import warnings -from contextlib import chdir +from contextlib_chdir import chdir from os import getenv, path from typing import TYPE_CHECKING, Any, Literal, NamedTuple diff --git a/sphinx/theming.py b/sphinx/theming.py index 7f1c53d1ebc..6b15fa6d97d 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -9,7 +9,10 @@ import shutil import sys import tempfile -import tomllib + +# import tomllib +import tomli as tomllib + from importlib.metadata import entry_points from pathlib import Path from typing import TYPE_CHECKING, Any diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index cd5619a729e..e9f3381c19c 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -59,7 +59,10 @@ def __call__( # NoQA: E704 Formatter: TypeAlias = DateFormatter | TimeFormatter | DatetimeFormatter -from datetime import UTC +# from datetime import UTC +from datetime import timezone + +UTC = timezone.utc logger = logging.getLogger(__name__) diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index 2e8d9e689f7..29a42968ddf 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -18,8 +18,9 @@ TypedDict, TypeVar, Union, - Unpack, + # Unpack, ) +from typing_extensions import Unpack from docutils import nodes from docutils.parsers.rst.states import Inliner diff --git a/tests/conftest.py b/tests/conftest.py index 8501825a025..02dfe8717a7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,8 @@ from types import SimpleNamespace from typing import TYPE_CHECKING +import contextlib_chdir + import docutils import pytest From c3a9e89ed4ecb4f470cbde4353b2e5dadf023a73 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Thu, 7 Nov 2024 10:58:46 +1300 Subject: [PATCH 07/13] Use getattr for accessing autosectionlabel_full_reference in latex and texinfo writers --- sphinx/writers/latex.py | 2 +- sphinx/writers/texinfo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index abdd83bb91f..9d7fea12b1a 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -490,7 +490,7 @@ 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 self.config.autosectionlabel_full_reference: + if getattr(self.config, 'autosectionlabel_full_reference', False): pass elif withdoc: id = self.curfilestack[-1] + ':' + id diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index aa719be638e..ef51b079515 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -550,7 +550,7 @@ def get_short_id(self, id: str) -> str: def add_anchor(self, id: str, node: Node) -> None: if id.startswith('index-'): return - if not self.config.autosectionlabel_full_reference: + 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) From ecc7b544aee153146995e4cf15b7cef29568be28 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Thu, 7 Nov 2024 12:08:39 +1300 Subject: [PATCH 08/13] Revert "python 3.10" This reverts commit 9072d6213b47b3a5e11f669a512ca2d64616a414. --- pyproject.toml | 2 +- sphinx/builders/linkcheck.py | 5 +---- sphinx/config.py | 2 +- sphinx/theming.py | 5 +---- sphinx/util/i18n.py | 5 +---- sphinx/util/typing.py | 3 +-- tests/conftest.py | 2 -- 7 files changed, 6 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ce48937eed3..2e97d5eee54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ urls.Download = "https://pypi.org/project/Sphinx/" urls.Homepage = "https://www.sphinx-doc.org/" urls."Issue tracker" = "https://github.com/sphinx-doc/sphinx/issues" license.text = "BSD-2-Clause" -requires-python = ">=3.10" +requires-python = ">=3.11" # Classifiers list: https://pypi.org/classifiers/ classifiers = [ diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 3ba5a06ddd1..fcf994e8e03 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -7,10 +7,7 @@ import re import socket import time - -# from enum import StrEnum -from strenum import StrEnum - +from enum import StrEnum from html.parser import HTMLParser from os import path from queue import PriorityQueue, Queue diff --git a/sphinx/config.py b/sphinx/config.py index ccf47306837..8700ed30054 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -6,7 +6,7 @@ import traceback import types import warnings -from contextlib_chdir import chdir +from contextlib import chdir from os import getenv, path from typing import TYPE_CHECKING, Any, Literal, NamedTuple diff --git a/sphinx/theming.py b/sphinx/theming.py index 6b15fa6d97d..7f1c53d1ebc 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -9,10 +9,7 @@ import shutil import sys import tempfile - -# import tomllib -import tomli as tomllib - +import tomllib from importlib.metadata import entry_points from pathlib import Path from typing import TYPE_CHECKING, Any diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index e9f3381c19c..cd5619a729e 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -59,10 +59,7 @@ def __call__( # NoQA: E704 Formatter: TypeAlias = DateFormatter | TimeFormatter | DatetimeFormatter -# from datetime import UTC -from datetime import timezone - -UTC = timezone.utc +from datetime import UTC logger = logging.getLogger(__name__) diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index 29a42968ddf..2e8d9e689f7 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -18,9 +18,8 @@ TypedDict, TypeVar, Union, - # Unpack, + Unpack, ) -from typing_extensions import Unpack from docutils import nodes from docutils.parsers.rst.states import Inliner diff --git a/tests/conftest.py b/tests/conftest.py index 02dfe8717a7..8501825a025 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,8 +7,6 @@ from types import SimpleNamespace from typing import TYPE_CHECKING -import contextlib_chdir - import docutils import pytest From fbbe0d39768f2d0eb388a24f2e3e84d3fa8101dc Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Thu, 7 Nov 2024 13:01:41 +1300 Subject: [PATCH 09/13] Use getattr for accessing autosectionlabel_full_reference in latex (missed one place) --- sphinx/writers/latex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 9d7fea12b1a..0966b85a4c6 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1973,7 +1973,7 @@ def visit_reference(self, node: Element) -> None: if hashindex == -1: # reference to the document id = uri[1:] + '::doc' - elif self.config.autosectionlabel_full_reference: + elif getattr(self.config, 'autosectionlabel_full_reference', False): id = uri[hashindex + 1 :] else: # reference to a label From 88e07543b11897f055b3d7b0fe149f0826a87e07 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Thu, 7 Nov 2024 13:40:06 +1300 Subject: [PATCH 10/13] use one-liner for constructing id_array parts --- sphinx/ext/autosectionlabel.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index e6d32bc3596..4a58bd267eb 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -57,9 +57,7 @@ def register_sections_as_label(app: Sphinx, doctree: nodes.document) -> None: if app.config.autosectionlabel_prefix_document: if app.config.autosectionlabel_full_reference: id_array = _get_all_node_parent_section_titles(node) - id_array.append(docname) - id_array.reverse() - id_array.append(ref_name) + id_array = [docname, *reversed(id_array), refname] # replace id_prefix temporarily id_prefix = settings.id_prefix settings.id_prefix = '.'.join(id_array) From febcecec7c478b8dbac59b553f58cca5dd65a887 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Thu, 7 Nov 2024 13:43:29 +1300 Subject: [PATCH 11/13] fix typo --- sphinx/ext/autosectionlabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index 4a58bd267eb..18140d4a345 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -57,7 +57,7 @@ def register_sections_as_label(app: Sphinx, doctree: nodes.document) -> None: if app.config.autosectionlabel_prefix_document: if app.config.autosectionlabel_full_reference: id_array = _get_all_node_parent_section_titles(node) - id_array = [docname, *reversed(id_array), refname] + id_array = [docname, *reversed(id_array), ref_name] # replace id_prefix temporarily id_prefix = settings.id_prefix settings.id_prefix = '.'.join(id_array) From d059cf8f174d3806a516462e3f718dec87be2647 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Fri, 29 Nov 2024 15:06:25 +1300 Subject: [PATCH 12/13] Add the generated full section label id to the list of node ids. This makes the full section id available to be used for cross references. Note: The original short id is still needed by other functionality. I.e. the full id cannot replace the short one, `node['ids'][0] = labelid` because then TOC and sectnum:: functionality is broken in the HTML output. --- sphinx/ext/autosectionlabel.py | 5 +- .../test_ext_autosectionlabel.py | 71 +++++++------------ 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index 18140d4a345..0ee747576a0 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -63,12 +63,15 @@ def register_sections_as_label(app: Sphinx, doctree: nodes.document) -> None: settings.id_prefix = '.'.join(id_array) labelid = make_id(app.env, doctree, '', '.'.join(id_array)) - node['ids'][0] = labelid 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: diff --git a/tests/test_extensions/test_ext_autosectionlabel.py b/tests/test_extensions/test_ext_autosectionlabel.py index f18726280da..c3c9c0678af 100644 --- a/tests/test_extensions/test_ext_autosectionlabel.py +++ b/tests/test_extensions/test_ext_autosectionlabel.py @@ -138,9 +138,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: content = file.read_text(encoding='utf8') html = ( + '

    ' 'Introduction of Sphinx' '.*' '
  • ' @@ -149,9 +148,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Installation' '.*' '
  • ' @@ -160,9 +158,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'For Windows users' '.*' '
  • ' @@ -171,9 +168,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Windows' '.*' '
  • ' @@ -182,9 +178,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Command' '.*' '
  • ' @@ -193,9 +188,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Command' '.*' '
  • ' @@ -204,9 +198,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Command' '.*' '
  • ' @@ -215,9 +208,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'For UNIX users' '.*' '
  • ' @@ -226,9 +218,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Linux' '.*' '
  • ' @@ -237,9 +228,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Command1' '.*' '
  • ' @@ -248,9 +238,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'FreeBSD' '.*' '
  • ' @@ -259,9 +248,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' '2nd Command' '.*' '
  • ' @@ -271,9 +259,8 @@ def _autosectionlabel_full_reference_html_index(file: Path) -> None: # for smart_quotes (refs: #4027) html = ( + '

    ' 'This one’s got an apostrophe' '.*' '
  • ' @@ -287,9 +274,8 @@ def _autosectionlabel_full_reference_html_windows(file: Path) -> None: content = file.read_text(encoding='utf8') html = ( + '

    ' 'For Windows users' '.*' '
  • ' @@ -298,9 +284,8 @@ def _autosectionlabel_full_reference_html_windows(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Windows' '.*' '
  • ' @@ -309,9 +294,8 @@ def _autosectionlabel_full_reference_html_windows(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Command' '.*' '
  • ' @@ -320,9 +304,8 @@ def _autosectionlabel_full_reference_html_windows(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Command' '.*' '
  • ' @@ -331,9 +314,8 @@ def _autosectionlabel_full_reference_html_windows(file: Path) -> None: assert re.search(html, content, re.DOTALL) html = ( + '

    ' 'Command' '.*' '
  • ' @@ -351,7 +333,7 @@ def test_autosectionlabel_full_reference_latex(app): latex = ( 'chapter{Installation}' - '.' + '.*' '.label{.detokenize{index.Installation}}' '.*' '{.hyperref..detokenize{index.Installation}.{.sphinxcrossref{.DUrole{std}{.DUrole{std-ref}{Installation}}}}}' @@ -360,7 +342,7 @@ def test_autosectionlabel_full_reference_latex(app): latex = ( 'subsubsection{Command}' - '.' + '.*' '.label{.detokenize{index.Installation.For-Windows-users.Windows.Command}}' '.*' '{.hyperref..detokenize{index.Installation.For-Windows-users.Windows.Command}.' @@ -370,7 +352,7 @@ def test_autosectionlabel_full_reference_latex(app): latex = ( 'subsubsection{Command}' - '.' + '.*' '.label{.detokenize{index.Installation.For-Windows-users.Windows.Command0}}' '.*' '{.hyperref..detokenize{index.Installation.For-Windows-users.Windows.Command0}.' @@ -380,7 +362,7 @@ def test_autosectionlabel_full_reference_latex(app): latex = ( 'subsubsection{Command}' - '.' + '.*' '.label{.detokenize{index.Installation.For-Windows-users.Windows.Command1}}' '.*' '{.hyperref..detokenize{index.Installation.For-Windows-users.Windows.Command1}.' @@ -398,7 +380,7 @@ def test_autosectionlabel_full_reference_texinfo(app): texinfo = ( '@node Installation,References,Directives,Top' '\n' - '@anchor{index Installation}@anchor{3}' + '@anchor{index Installation}@anchor{5}@anchor{installation}@anchor{6}' '\n' '@unnumbered Installation' ) @@ -407,7 +389,7 @@ def test_autosectionlabel_full_reference_texinfo(app): texinfo = ( '@node Command,Command<2>,,Windows' '\n' - '@anchor{index Installation For-Windows-users Windows Command}@anchor{6}' + '@anchor{command}@anchor{b}@anchor{index Installation For-Windows-users Windows Command}@anchor{c}' '\n' '@subsection Command' ) @@ -416,7 +398,7 @@ def test_autosectionlabel_full_reference_texinfo(app): texinfo = ( '@node Command<2>,Command<3>,Command,Windows' '\n' - '@anchor{index Installation For-Windows-users Windows Command0}@anchor{7}' + '@anchor{id1}@anchor{d}@anchor{index Installation For-Windows-users Windows Command0}@anchor{e}' '\n' '@subsection Command' ) @@ -425,8 +407,9 @@ def test_autosectionlabel_full_reference_texinfo(app): texinfo = ( '@node Command<3>,,Command<2>,Windows' '\n' - '@anchor{index Installation For-Windows-users Windows Command1}@anchor{8}' + '@anchor{id2}@anchor{f}@anchor{index Installation For-Windows-users Windows Command1}@anchor{10}' '\n' '@subsection Command' ) assert texinfo in content + From a80ef4eb92dc35bad22c0b0ee0dc92fd3dd3fd78 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Fri, 29 Nov 2024 16:06:51 +1300 Subject: [PATCH 13/13] Remove blank line --- tests/test_extensions/test_ext_autosectionlabel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_extensions/test_ext_autosectionlabel.py b/tests/test_extensions/test_ext_autosectionlabel.py index c3c9c0678af..923a9ac2f9c 100644 --- a/tests/test_extensions/test_ext_autosectionlabel.py +++ b/tests/test_extensions/test_ext_autosectionlabel.py @@ -412,4 +412,3 @@ def test_autosectionlabel_full_reference_texinfo(app): '@subsection Command' ) assert texinfo in content -