Skip to content

Instantly share code, notes, and snippets.

@dzhu
Created March 3, 2020 03:52
Show Gist options
  • Save dzhu/4477338e34620d141bc71f4dc214ea47 to your computer and use it in GitHub Desktop.
Save dzhu/4477338e34620d141bc71f4dc214ea47 to your computer and use it in GitHub Desktop.
"""
Sphinx is hardcoded to interpret links to downloadable files relative to the root of the docs
source tree. However, the downloadable files we want to use are themselves generated at build time,
and we would therefore like them to be separate from the source. This module is a Sphinx plugin that
replaces the normal interpretation of links, causing Sphinx to look for downloads relative to a
different directory (which is set in `conf.py`).
"""
import logging
import os
import types
from typing import Any, Dict
from docutils import nodes
from sphinx import addnodes, application
from sphinx.environment.collectors import asset
from sphinx.locale import __
logger = logging.getLogger(__name__)
class DownloadExternalFileCollector(asset.DownloadFileCollector):
def process_doc(
self: asset.DownloadFileCollector, app: application.Sphinx, doctree: nodes.document
) -> None:
"""
This function is different from the original method only in doing some surgery on the paths
it finds when a separate root directory is configured.
Compare to the original at https://github.com/sphinx-doc/sphinx/blob/cdc7cc6fb3e2ade64149a5bed9b3b5be0132ce47/sphinx/environment/collectors/asset.py#L122
"""
for node in doctree.traverse(addnodes.download_reference):
targetname = node["reftarget"]
if "://" in targetname:
node["refuri"] = targetname
else:
rel_filename, filename = app.env.relfn2path(targetname, app.env.docname)
if app.config.downloads_root:
filename = os.path.abspath(
os.path.join(app.config.downloads_root, rel_filename)
)
rel_filename = os.path.relpath(filename, app.env.srcdir)
app.env.dependencies[app.env.docname].add(rel_filename)
if not os.access(filename, os.R_OK):
logger.warning(
__("download file not readable: %s") % filename,
location=node,
type="download",
subtype="not_readable",
)
continue
node["filename"] = app.env.dlfiles.add_file(app.env.docname, rel_filename)
def setup(app: application.Sphinx) -> Dict[str, Any]:
app.add_config_value("downloads_root", None, "html")
# Disable the old instance of DownloadFileCollector and replace it with ours.
for event in app.events.listeners.values():
for listener_id, callback in list(event.items()):
if isinstance(callback, types.MethodType) and isinstance(
callback.__self__, asset.DownloadFileCollector
):
del event[listener_id]
app.add_env_collector(DownloadExternalFileCollector)
return {
"version": "0",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment