From 7edc05b1ff4afe17492738a672795016e0a9f339 Mon Sep 17 00:00:00 2001 From: Kilian Paul <81327374+Darlokt@users.noreply.github.com> Date: Fri, 18 Sep 2026 01:58:23 +0200 Subject: [PATCH 1/4] Use canonical RFC URLs independently of Docutils --- CHANGES.rst | 5 ++++ sphinx/environment/__init__.py | 2 +- sphinx/roles.py | 10 +++---- .../test_builders/test_build_html_5_output.py | 4 +-- tests/test_markup/test_markup.py | 8 +++--- tests/test_roles.py | 26 ++++++++++++++++++- 6 files changed, 42 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ad6d698341a..730675e4eaf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,11 @@ Release 9.1.1 (in development) Bugs fixed ---------- +* #14659: Use the canonical ``https://www.rfc-editor.org/info/`` base URL for + the :rst:role:`rfc` role and make generated RFC URLs independent of + Docutils' URL template. + Patch by Kilian Paul + * #14465: LaTeX: PDF build crash since LaTeX June 2026 release if tables are styled with ``'colorrows'`` (which is the default). Patch by Jean-François B. diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index c5cc77139f7..4e8b19f52ff 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -63,7 +63,7 @@ 'cloak_email_addresses': True, 'pep_base_url': 'https://peps.python.org/', 'pep_references': None, - 'rfc_base_url': 'https://datatracker.ietf.org/doc/html/', + 'rfc_base_url': 'https://www.rfc-editor.org/info/', 'rfc_references': None, 'input_encoding': 'utf-8-sig', 'doctitle_xform': False, diff --git a/sphinx/roles.py b/sphinx/roles.py index fdb997314fe..9717a90eaea 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -360,11 +360,11 @@ def run(self) -> tuple[list[Node], list[system_message]]: def build_uri(self) -> str: base_url = self.inliner.document.settings.rfc_base_url - ret = self.target.partition('#') - if ret[1]: - return base_url + self.inliner.rfc_url % int(ret[0]) + '#' + ret[2] - else: - return base_url + self.inliner.rfc_url % int(ret[0]) + number, separator, anchor = self.target.partition('#') + uri = f'{base_url}rfc{int(number)}/' + if separator: + uri += f'#{anchor}' + return uri def _format_rfc_target(target: str, /) -> str: diff --git a/tests/test_builders/test_build_html_5_output.py b/tests/test_builders/test_build_html_5_output.py index b712d1415a3..8fb4378bf52 100644 --- a/tests/test_builders/test_build_html_5_output.py +++ b/tests/test_builders/test_build_html_5_output.py @@ -147,13 +147,13 @@ def checker(nodes: Iterable[Element]) -> Literal[True]: ), ( 'markup.html', - ".//a[@href='https://datatracker.ietf.org/doc/html/rfc1.html']" + ".//a[@href='https://www.rfc-editor.org/info/rfc1/']" "[@class='rfc reference external']/strong", 'RFC 1', ), ( 'markup.html', - ".//a[@href='https://datatracker.ietf.org/doc/html/rfc1.html']" + ".//a[@href='https://www.rfc-editor.org/info/rfc1/']" "[@class='rfc reference external']/strong", 'Request for Comments #1', ), diff --git a/tests/test_markup/test_markup.py b/tests/test_markup/test_markup.py index 4889ed6daa8..30c07d989a0 100644 --- a/tests/test_markup/test_markup.py +++ b/tests/test_markup/test_markup.py @@ -212,12 +212,12 @@ def rst_to_latex(rst: str, *, app: SphinxTestApp) -> str: ':rfc:`2324`', ( '

RFC 2324

' + 'href="https://www.rfc-editor.org/info/rfc2324/">RFC 2324

' ), ( '\\sphinxAtStartPar\n' '\\index{RFC@\\spxentry{RFC}!RFC 2324@\\spxentry{RFC 2324}}' - '\\sphinxhref{https://datatracker.ietf.org/doc/html/rfc2324.html}' + '\\sphinxhref{https://www.rfc-editor.org/info/rfc2324/}' '{\\sphinxstylestrong{RFC 2324}}' ), ), @@ -226,13 +226,13 @@ def rst_to_latex(rst: str, *, app: SphinxTestApp) -> str: ':rfc:`2324#section-1`', ( '

' + 'href="https://www.rfc-editor.org/info/rfc2324/#section-1">' 'RFC 2324 Section 1

' ), ( '\\sphinxAtStartPar\n' '\\index{RFC@\\spxentry{RFC}!RFC 2324 Section 1@\\spxentry{RFC 2324 Section 1}}' - '\\sphinxhref{https://datatracker.ietf.org/doc/html/rfc2324.html\\#section-1}' + '\\sphinxhref{https://www.rfc-editor.org/info/rfc2324/\\#section-1}' '{\\sphinxstylestrong{RFC 2324 Section 1}}' ), ), diff --git a/tests/test_roles.py b/tests/test_roles.py index e5ba9b3e5b6..f4c01b91672 100644 --- a/tests/test_roles.py +++ b/tests/test_roles.py @@ -2,14 +2,19 @@ from __future__ import annotations +from types import SimpleNamespace +from typing import TYPE_CHECKING, cast from unittest.mock import Mock import pytest from docutils import nodes -from sphinx.roles import EmphasizedLiteral, _format_rfc_target +from sphinx.roles import RFC, EmphasizedLiteral, _format_rfc_target from sphinx.testing.util import assert_node +if TYPE_CHECKING: + from docutils.parsers.rst.states import Inliner + def test_samp() -> None: emph_literal_role = EmphasizedLiteral() @@ -137,3 +142,22 @@ def test_samp() -> None: ) def test_format_rfc_target(target: str, expected_output: str) -> None: assert _format_rfc_target(target) == expected_output + + +@pytest.mark.parametrize( + ('target', 'expected_uri'), + [ + ('2324', 'https://example.com/rfc/rfc2324/'), + ('2324#section-1', 'https://example.com/rfc/rfc2324/#section-1'), + ('2324#', 'https://example.com/rfc/rfc2324/#'), + ], +) +def test_rfc_build_uri(target: str, expected_uri: str) -> None: + role = RFC() + settings = SimpleNamespace(rfc_base_url='https://example.com/rfc/') + role.inliner = cast( + 'Inliner', SimpleNamespace(document=SimpleNamespace(settings=settings)) + ) + role.target = target + + assert role.build_uri() == expected_uri From c49551c695a61111547b46fa0e56648742d87d07 Mon Sep 17 00:00:00 2001 From: Kilian Paul <81327374+Darlokt@users.noreply.github.com> Date: Fri, 18 Sep 2026 02:00:01 +0200 Subject: [PATCH 2/4] 67 Bump ENV_Version to 67 to invalidate cached build environments. --- sphinx/environment/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 4e8b19f52ff..35d76ecb643 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -76,7 +76,7 @@ # This is increased every time an environment attribute is added # or changed to properly invalidate pickle files. -ENV_VERSION = 66 +ENV_VERSION = 67 # config status CONFIG_UNSET = -1 From feceebdcdbe9c4023efe441d521b36735dabe5ff Mon Sep 17 00:00:00 2001 From: Kilian Paul <81327374+Darlokt@users.noreply.github.com> Date: Fri, 18 Sep 2026 03:26:44 +0200 Subject: [PATCH 3/4] Add authors --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index b702c64c0f9..581d1fd4e68 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -81,6 +81,7 @@ Contributors * Justus Magin -- napoleon improvements * Kazuya Take -- ``sphinx.testing.path`` bug fix * Kevin Dunn -- MathJax extension +* Kilian Paul -- Docutils compatibility, intl tests, and viewcode fixes * KINEBUCHI Tomohiko -- typing Sphinx as well as docutils * Kurt McKee -- documentation updates * Lars Hupfeldt Nielsen - OpenSSL FIPS mode md5 bug fix From 132667090f23beabf85a570747679aeec6803b90 Mon Sep 17 00:00:00 2001 From: Kilian Paul <81327374+Darlokt@users.noreply.github.com> Date: Fri, 18 Sep 2026 03:45:46 +0200 Subject: [PATCH 4/4] Update JavaScript search fixtures for environment version 67 --- tests/js/fixtures/cpp/searchindex.js | 2 +- tests/js/fixtures/multiterm/searchindex.js | 2 +- tests/js/fixtures/partial/searchindex.js | 2 +- tests/js/fixtures/titles/searchindex.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/js/fixtures/cpp/searchindex.js b/tests/js/fixtures/cpp/searchindex.js index 1dac186a7e7..da4c01586b9 100644 --- a/tests/js/fixtures/cpp/searchindex.js +++ b/tests/js/fixtures/cpp/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles":{},"docnames":["index"],"envversion":{"sphinx":66,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["index.rst"],"indexentries":{"sphinx (c++ class)":[[0,"_CPPv46Sphinx",false]]},"objects":{"":[[0,0,1,"_CPPv46Sphinx","Sphinx"]]},"objnames":{"0":["cpp","class","C++ class"]},"objtypes":{"0":"cpp:class"},"terms":{"The":0,"This":0,"becaus":0,"c":0,"can":0,"cardin":0,"challeng":0,"charact":0,"class":0,"descript":0,"drop":0,"engin":0,"fixtur":0,"frequent":0,"generat":0,"index":0,"inflat":0,"mathemat":0,"occur":0,"often":0,"project":0,"punctuat":0,"queri":0,"relat":0,"sampl":0,"search":0,"size":0,"sphinx":0,"term":0,"token":0,"use":0,"web":0},"titles":[""],"titleterms":{}}) \ No newline at end of file +Search.setIndex({"alltitles":{},"docnames":["index"],"envversion":{"sphinx":67,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["index.rst"],"indexentries":{"sphinx (c++ class)":[[0,"_CPPv46Sphinx",false]]},"objects":{"":[[0,0,1,"_CPPv46Sphinx","Sphinx"]]},"objnames":{"0":["cpp","class","C++ class"]},"objtypes":{"0":"cpp:class"},"terms":{"The":0,"This":0,"becaus":0,"c":0,"can":0,"cardin":0,"challeng":0,"charact":0,"class":0,"descript":0,"drop":0,"engin":0,"fixtur":0,"frequent":0,"generat":0,"index":0,"inflat":0,"mathemat":0,"occur":0,"often":0,"project":0,"punctuat":0,"queri":0,"relat":0,"sampl":0,"search":0,"size":0,"sphinx":0,"term":0,"token":0,"use":0,"web":0},"titles":[""],"titleterms":{}}) \ No newline at end of file diff --git a/tests/js/fixtures/multiterm/searchindex.js b/tests/js/fixtures/multiterm/searchindex.js index bd732522b3d..5991620b3a9 100644 --- a/tests/js/fixtures/multiterm/searchindex.js +++ b/tests/js/fixtures/multiterm/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles":{"Main Page":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":66,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["index.rst"],"indexentries":{},"objects":{},"objnames":{},"objtypes":{},"terms":{"At":0,"This":0,"adjac":0,"appear":0,"applic":0,"built":0,"can":0,"check":0,"contain":0,"document":0,"doesn":0,"fixtur":0,"format":0,"function":0,"futur":0,"html":0,"includ":0,"match":0,"messag":0,"multipl":0,"multiterm":0,"order":0,"output":0,"perform":0,"perhap":0,"phrase":0,"project":0,"queri":0,"requir":0,"search":0,"success":0,"support":0,"t":0,"term":0,"test":0,"time":0,"use":0,"will":0,"write":0},"titles":["Main Page"],"titleterms":{"main":0,"page":0}}) \ No newline at end of file +Search.setIndex({"alltitles":{"Main Page":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":67,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["index.rst"],"indexentries":{},"objects":{},"objnames":{},"objtypes":{},"terms":{"At":0,"This":0,"adjac":0,"appear":0,"applic":0,"built":0,"can":0,"check":0,"contain":0,"document":0,"doesn":0,"fixtur":0,"format":0,"function":0,"futur":0,"html":0,"includ":0,"match":0,"messag":0,"multipl":0,"multiterm":0,"order":0,"output":0,"perform":0,"perhap":0,"phrase":0,"project":0,"queri":0,"requir":0,"search":0,"success":0,"support":0,"t":0,"term":0,"test":0,"time":0,"use":0,"will":0,"write":0},"titles":["Main Page"],"titleterms":{"main":0,"page":0}}) \ No newline at end of file diff --git a/tests/js/fixtures/partial/searchindex.js b/tests/js/fixtures/partial/searchindex.js index 8a65718e2ea..a9d3ec42130 100644 --- a/tests/js/fixtures/partial/searchindex.js +++ b/tests/js/fixtures/partial/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles":{"sphinx_utils module":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":66,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["index.rst"],"indexentries":{},"objects":{},"objnames":{},"objtypes":{},"terms":{"This":0,"built":0,"confirm":0,"document":0,"function":0,"html":0,"includ":0,"input":0,"javascript":0,"match":0,"partial":0,"possibl":0,"project":0,"provid":0,"restructuredtext":0,"sampl":0,"search":0,"term":0,"titl":0,"use":0},"titles":["sphinx_utils module"],"titleterms":{"modul":0,"sphinx_util":0}}) \ No newline at end of file +Search.setIndex({"alltitles":{"sphinx_utils module":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":67,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["index.rst"],"indexentries":{},"objects":{},"objnames":{},"objtypes":{},"terms":{"This":0,"built":0,"confirm":0,"document":0,"function":0,"html":0,"includ":0,"input":0,"javascript":0,"match":0,"partial":0,"possibl":0,"project":0,"provid":0,"restructuredtext":0,"sampl":0,"search":0,"term":0,"titl":0,"use":0},"titles":["sphinx_utils module"],"titleterms":{"modul":0,"sphinx_util":0}}) \ No newline at end of file diff --git a/tests/js/fixtures/titles/searchindex.js b/tests/js/fixtures/titles/searchindex.js index fe325c7742d..0d661568511 100644 --- a/tests/js/fixtures/titles/searchindex.js +++ b/tests/js/fixtures/titles/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles":{"Main Page":[[0,null]],"Relevance":[[0,"relevance"],[1,null]],"Result Scoring":[[0,"result-scoring"]]},"docnames":["index","relevance"],"envversion":{"sphinx":66,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["index.rst","relevance.rst"],"indexentries":{"example (class in relevance)":[[0,"relevance.Example",false]],"module":[[0,"module-relevance",false]],"relevance":[[0,"index-1",false],[0,"module-relevance",false]],"relevance (relevance.example attribute)":[[0,"relevance.Example.relevance",false]],"scoring":[[0,"index-0",true]]},"objects":{"":[[0,0,0,"-","relevance"]],"relevance":[[0,1,1,"","Example"]],"relevance.Example":[[0,2,1,"","relevance"]]},"objnames":{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"]},"objtypes":{"0":"py:module","1":"py:class","2":"py:attribute"},"terms":{"A":1,"By":0,"For":[0,1],"In":[0,1],"This":0,"align":0,"also":1,"answer":0,"appear":1,"area":0,"ask":0,"assign":0,"attempt":0,"attribut":0,"built":1,"can":[0,1],"class":0,"code":[0,1],"collect":0,"consid":1,"contain":0,"context":0,"corpus":1,"demonstr":0,"describ":1,"detail":1,"determin":[0,1],"docstr":0,"document":[0,1],"domain":1,"dure":0,"engin":0,"evalu":0,"exampl":[0,1],"extract":0,"feedback":0,"find":0,"found":0,"function":1,"handl":0,"happen":1,"head":0,"help":0,"high":[0,1],"improv":0,"inform":0,"intend":0,"issu":[0,1],"knowledg":0,"languag":1,"less":1,"like":[0,1],"mani":0,"match":0,"mention":1,"name":[0,1],"numer":0,"object":0,"often":0,"one":[0,1],"onli":[0,1],"order":0,"page":1,"part":1,"particular":0,"present":0,"printf":1,"program":1,"project":0,"queri":[0,1],"question":0,"re":0,"relat":0,"research":0,"result":1,"retriev":0,"s":[0,1],"say":0,"search":[0,1],"seem":0,"softwar":1,"sphinx":0,"straightforward":1,"subject":0,"subsect":0,"term":[0,1],"test":0,"text":0,"time":0,"titl":0,"two":0,"typic":0,"use":0,"user":[0,1],"whether":1,"will":0,"within":0,"word":0},"titles":["Main Page","Relevance"],"titleterms":{"main":0,"page":0,"relev":[0,1],"result":0,"score":0}}) \ No newline at end of file +Search.setIndex({"alltitles":{"Main Page":[[0,null]],"Relevance":[[0,"relevance"],[1,null]],"Result Scoring":[[0,"result-scoring"]]},"docnames":["index","relevance"],"envversion":{"sphinx":67,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["index.rst","relevance.rst"],"indexentries":{"example (class in relevance)":[[0,"relevance.Example",false]],"module":[[0,"module-relevance",false]],"relevance":[[0,"index-1",false],[0,"module-relevance",false]],"relevance (relevance.example attribute)":[[0,"relevance.Example.relevance",false]],"scoring":[[0,"index-0",true]]},"objects":{"":[[0,0,0,"-","relevance"]],"relevance":[[0,1,1,"","Example"]],"relevance.Example":[[0,2,1,"","relevance"]]},"objnames":{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"]},"objtypes":{"0":"py:module","1":"py:class","2":"py:attribute"},"terms":{"A":1,"By":0,"For":[0,1],"In":[0,1],"This":0,"align":0,"also":1,"answer":0,"appear":1,"area":0,"ask":0,"assign":0,"attempt":0,"attribut":0,"built":1,"can":[0,1],"class":0,"code":[0,1],"collect":0,"consid":1,"contain":0,"context":0,"corpus":1,"demonstr":0,"describ":1,"detail":1,"determin":[0,1],"docstr":0,"document":[0,1],"domain":1,"dure":0,"engin":0,"evalu":0,"exampl":[0,1],"extract":0,"feedback":0,"find":0,"found":0,"function":1,"handl":0,"happen":1,"head":0,"help":0,"high":[0,1],"improv":0,"inform":0,"intend":0,"issu":[0,1],"knowledg":0,"languag":1,"less":1,"like":[0,1],"mani":0,"match":0,"mention":1,"name":[0,1],"numer":0,"object":0,"often":0,"one":[0,1],"onli":[0,1],"order":0,"page":1,"part":1,"particular":0,"present":0,"printf":1,"program":1,"project":0,"queri":[0,1],"question":0,"re":0,"relat":0,"research":0,"result":1,"retriev":0,"s":[0,1],"say":0,"search":[0,1],"seem":0,"softwar":1,"sphinx":0,"straightforward":1,"subject":0,"subsect":0,"term":[0,1],"test":0,"text":0,"time":0,"titl":0,"two":0,"typic":0,"use":0,"user":[0,1],"whether":1,"will":0,"within":0,"word":0},"titles":["Main Page","Relevance"],"titleterms":{"main":0,"page":0,"relev":[0,1],"result":0,"score":0}}) \ No newline at end of file