|
| 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 | + </head> |
| 22 | + </html> |
| 23 | +
|
| 24 | +If you need to redirect across subdirectory trees, that works as well. For |
| 25 | +instance if ``doc/topic/subdir1/old-page.rst`` is now found at |
| 26 | +``doc/topic/subdir2/new-page.rst`` then ``new-page.rst`` just lists the |
| 27 | +full path:: |
| 28 | +
|
| 29 | + .. redirect-from:: /topic/subdir1/old-page.rst |
| 30 | +
|
| 31 | +""" |
| 32 | + |
| 33 | +from pathlib import Path |
| 34 | +from docutils.parsers.rst import Directive |
| 35 | +from sphinx.util import logging |
| 36 | + |
| 37 | +logger = logging.getLogger(__name__) |
| 38 | + |
| 39 | + |
| 40 | +HTML_TEMPLATE = """<html> |
| 41 | + <head> |
| 42 | + <meta http-equiv="refresh" content="0; url={v}"> |
| 43 | + </head> |
| 44 | +</html> |
| 45 | +""" |
| 46 | + |
| 47 | + |
| 48 | +def setup(app): |
| 49 | + RedirectFrom.app = app |
| 50 | + app.add_directive("redirect-from", RedirectFrom) |
| 51 | + app.connect("build-finished", _generate_redirects) |
| 52 | + |
| 53 | + |
| 54 | +class RedirectFrom(Directive): |
| 55 | + required_arguments = 1 |
| 56 | + redirects = {} |
| 57 | + |
| 58 | + def run(self): |
| 59 | + redirected_doc, = self.arguments |
| 60 | + env = self.app.env |
| 61 | + builder = self.app.builder |
| 62 | + current_doc = env.path2doc(self.state.document.current_source) |
| 63 | + redirected_reldoc, _ = env.relfn2path(redirected_doc, current_doc) |
| 64 | + if redirected_reldoc in self.redirects: |
| 65 | + raise ValueError( |
| 66 | + f"{redirected_reldoc} is already noted as redirecting to " |
| 67 | + f"{self.redirects[redirected_reldoc]}") |
| 68 | + self.redirects[redirected_reldoc] = builder.get_relative_uri( |
| 69 | + redirected_reldoc, current_doc) |
| 70 | + return [] |
| 71 | + |
| 72 | + |
| 73 | +def _generate_redirects(app, exception): |
| 74 | + builder = app.builder |
| 75 | + if builder.name != "html" or exception: |
| 76 | + return |
| 77 | + for k, v in RedirectFrom.redirects.items(): |
| 78 | + p = Path(app.outdir, k + builder.out_suffix) |
| 79 | + if p.is_file(): |
| 80 | + logger.warning(f'A redirect-from directive is trying to create ' |
| 81 | + f'{p}, but that file already exists (perhaps ' |
| 82 | + f'you need to run "make clean")') |
| 83 | + else: |
| 84 | + p.parent.mkdir(parents=True, exist_ok=True) |
| 85 | + with p.open("x") as file: |
| 86 | + logger.info(f'making refresh html file: {k} redirect to {v}') |
| 87 | + file.write(HTML_TEMPLATE.format(v=v)) |
0 commit comments