-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Expand file tree
/
Copy pathredirect_from.py
More file actions
132 lines (105 loc) · 4.2 KB
/
redirect_from.py
File metadata and controls
132 lines (105 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""
Redirecting old docs to new location
====================================
If an rst file is moved or its content subsumed in a different file, it
is desirable to redirect the old file to the new or existing file. This
extension enables this with a simple html refresh.
For example suppose ``doc/topic/old-page.rst`` is removed and its content
included in ``doc/topic/new-page.rst``. We use the ``redirect-from``
directive in ``doc/topic/new-page.rst``::
.. redirect-from:: /topic/old-page
This creates in the build directory a file ``build/html/topic/old-page.html``
that contains a relative refresh::
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=new-page.html">
</head>
</html>
If you need to redirect across subdirectory trees, that works as well. For
instance if ``doc/topic/subdir1/old-page.rst`` is now found at
``doc/topic/subdir2/new-page.rst`` then ``new-page.rst`` just lists the
full path::
.. redirect-from:: /topic/subdir1/old-page.rst
"""
from pathlib import Path
from sphinx.util.docutils import SphinxDirective
from sphinx.domains import Domain
from sphinx.util import logging
logger = logging.getLogger(__name__)
HTML_TEMPLATE = """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url={v}">
</head>
</html>
"""
def setup(app):
app.add_directive("redirect-from", RedirectFrom)
app.add_domain(RedirectFromDomain)
app.connect("builder-inited", _clear_redirects)
app.connect("build-finished", _generate_redirects)
metadata = {'parallel_read_safe': True}
return metadata
class RedirectFromDomain(Domain):
"""
The sole purpose of this domain is a parallel_read_safe data store for the
redirects mapping.
"""
name = 'redirect_from'
label = 'redirect_from'
@property
def redirects(self):
"""The mapping of the redirects."""
return self.data.setdefault('redirects', {})
def clear_doc(self, docname):
self.redirects.pop(docname, None)
def merge_domaindata(self, docnames, otherdata):
for src, dst in otherdata['redirects'].items():
if src not in self.redirects:
self.redirects[src] = dst
elif self.redirects[src] != dst:
raise ValueError(
f"Inconsistent redirections from {src} to "
f"{self.redirects[src]} and {otherdata['redirects'][src]}")
class RedirectFrom(SphinxDirective):
required_arguments = 1
def run(self):
redirected_doc, = self.arguments
domain = self.env.get_domain('redirect_from')
current_doc = self.env.path2doc(self.state.document.current_source)
redirected_reldoc, _ = self.env.relfn2path(redirected_doc, current_doc)
if (
redirected_reldoc in domain.redirects
and domain.redirects[redirected_reldoc] != current_doc
):
raise ValueError(
f"{redirected_reldoc} is already noted as redirecting to "
f"{domain.redirects[redirected_reldoc]}\n"
f"Cannot also redirect it to {current_doc}"
)
domain.redirects[redirected_reldoc] = current_doc
return []
def _generate_redirects(app, exception):
builder = app.builder
if builder.name != "html" or exception:
return
for k, v in app.env.get_domain('redirect_from').redirects.items():
p = Path(app.outdir, k + builder.out_suffix)
html = HTML_TEMPLATE.format(v=builder.get_relative_uri(k, v))
if p.is_file():
if p.read_text() != html:
logger.warning('A redirect-from directive is trying to '
'create %s, but that file already exists '
'(perhaps you need to run "make clean")', p)
else:
logger.info('making refresh html file: %s redirect to %s', k, v)
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(html, encoding='utf-8')
def _clear_redirects(app):
domain = app.env.get_domain('redirect_from')
if domain.redirects:
logger.info('clearing cached redirects')
domain.redirects.clear()