Skip to content

Commit 5f6d7d2

Browse files
jklymakanntzer
andcommitted
ENH: add redirect_from sphinx extension
Co-authored-by: Antony Lee <anntzer.lee@gmail.com>
1 parent 9728e7e commit 5f6d7d2

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
'sphinxext.missing_references',
6464
'sphinxext.mock_gui_toolkits',
6565
'sphinxext.skip_deprecated',
66+
'sphinxext.redirect_from',
6667
'sphinx_copybutton',
6768
'sphinx_reredirects'
6869
]

doc/sphinxext/redirect_from.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
Redirecting old docs to new location
3+
====================================
4+
5+
If an rst file is moved or its content subsumed in a different file, it
6+
is desireable to redirect the old file to the new or existing file. This
7+
extension enables this with a simple html refresh.
8+
9+
For example suppose ``doc/topic/old-page.rst`` is removed and its content
10+
included in ``doc/topic/new-page.rst``. We use the ``redirect_from``
11+
directive in ``doc/topic/new-page.rst``::
12+
13+
.. redirect-from:: /topic/old-page
14+
15+
This creates in the build directory a file ``build/html/topic/old-page.html``
16+
that contains a relative refresh::
17+
18+
<html>
19+
<head>
20+
<meta http-equiv="refresh" content="0; url=new-page.html">
21+
<link rel="canonical" href="new-page.html">
22+
</head>
23+
</html>
24+
25+
If you need to redirect across subdirectory trees, that works as well. For
26+
instance if ``doc/topic/subdir1/old-page.rst`` is now found at
27+
``doc/topic/subdir2/new-page.rst`` then ``new-page.rst`` just lists the
28+
full path::
29+
30+
.. redirect-from:: /topic/subdir1/old-page.rst
31+
32+
"""
33+
34+
from pathlib import Path
35+
from docutils.parsers.rst import Directive
36+
from sphinx.util import logging
37+
38+
logger = logging.getLogger(__name__)
39+
40+
41+
HTML_TEMPLATE = """<html>
42+
<head>
43+
<meta http-equiv="refresh" content="0; url={v}">
44+
<link rel="canonical" href="{v}">
45+
</head>
46+
</html>
47+
"""
48+
49+
50+
def setup(app):
51+
RedirectFrom.app = app
52+
app.add_directive("redirect-from", RedirectFrom)
53+
app.connect("build-finished", _generate_redirects)
54+
55+
56+
class RedirectFrom(Directive):
57+
required_arguments = 1
58+
redirects = {}
59+
60+
def run(self):
61+
redirected_doc, = self.arguments
62+
env = self.app.env
63+
builder = self.app.builder
64+
current_doc = env.path2doc(self.state.document.current_source)
65+
redirected_reldoc, _ = env.relfn2path(redirected_doc, current_doc)
66+
if redirected_reldoc in self.redirects:
67+
raise ValueError(
68+
f"{redirected_reldoc} is already noted as redirecting to "
69+
f"{self.redirects[redirected_reldoc]}")
70+
self.redirects[redirected_reldoc] = builder.get_relative_uri(
71+
redirected_reldoc, current_doc)
72+
return []
73+
74+
75+
def _generate_redirects(app, exception):
76+
builder = app.builder
77+
if builder.name != "html" or exception:
78+
return
79+
for k, v in RedirectFrom.redirects.items():
80+
with Path(app.outdir, k + builder.out_suffix).open("w") as file:
81+
logger.info('making refresh html file: ' + k + ' redirect to ' + v)
82+
file.write(HTML_TEMPLATE.format(v=v))

0 commit comments

Comments
 (0)