File: redirect_from.py

package info (click to toggle)
matplotlib 3.6.3-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 76,280 kB
  • sloc: python: 133,763; cpp: 66,599; objc: 1,699; ansic: 1,367; javascript: 765; makefile: 153; sh: 48
file content (121 lines) | stat: -rw-r--r-- 3,910 bytes parent folder | download
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
"""
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 docutils.parsers.rst import Directive
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):
    RedirectFrom.app = app
    app.add_directive("redirect-from", RedirectFrom)
    app.add_domain(RedirectFromDomain)
    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, docnames):
        self.redirects.clear()

    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(Directive):
    required_arguments = 1

    def run(self):
        redirected_doc, = self.arguments
        env = self.app.env
        domain = env.get_domain('redirect_from')
        current_doc = env.path2doc(self.state.document.current_source)
        redirected_reldoc, _ = env.relfn2path(redirected_doc, current_doc)
        if redirected_reldoc in domain.redirects:
            raise ValueError(
                f"{redirected_reldoc} is already noted as redirecting to "
                f"{domain.redirects[redirected_reldoc]}")
        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(f'A redirect-from directive is trying to '
                               f'create {p}, but that file already exists '
                               f'(perhaps you need to run "make clean")')
        else:
            logger.info(f'making refresh html file: {k} redirect to {v}')
            p.parent.mkdir(parents=True, exist_ok=True)
            p.write_text(html, encoding='utf-8')