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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
"""Sphinx extension to enhance intersphinx support.
This fixes some reference issues with :rst:role:`option` (see
https://github.com/sphinx-doc/sphinx/pull/3769 for the equivalent upstream
fix).
It also introduces a ``.. default-intersphinx::`` directive that allows for
specifying one or more intersphinx set prefixes that should be tried if a
reference could not be found. For example::
.. default-intersphinx:: myapp1.5 python
:ref:`some-reference`
This does affect the process by which missing references are located. If an
unprefixed reference is used, it will only match if the prefix is in the list
above, which differs from the default behavior of looking through all
intersphinx mappings.
Setup
=====
This extension must be added to ``exetnsions`` in :file:`conf.py` after the
:py:mod:`sphinx.ext.intersphinx` extension is added. For example::
extensions = [
...
'sphinx.ext.intersphinx',
'beanbag_docutils.sphinx.ext.intersphinx',
...
]
"""
import re
from docutils.parsers.rst import Directive
from sphinx.errors import ExtensionError
from sphinx.ext import intersphinx
class DefaultIntersphinx(Directive):
"""Specifies one or more default intersphinx sets to use."""
required_arguments = 1
optional_arguments = 100
SPLIT_RE = re.compile(r',\s*')
def run(self):
"""Run the directive.
Returns:
list:
An empty list, always.
"""
env = self.state.document.settings.env
env.metadata[env.docname]['default-intersphinx-prefixes'] = \
self.arguments
return []
def _on_missing_reference(app, env, node, contnode):
"""Handler for missing references.
This will attempt to fix references to options and then attempt to
apply default intersphinx prefixes (if needed) before resolving a
reference using intersphinx.
Args:
app (sphinx.application.Sphinx):
The Sphinx application processing the document.
env (sphinx.environment.BuildEnvironment):
The environment for this doc build.
node (sphinx.addnodes.pending_xref):
The pending reference to resolve.
contnode (docutils.nodes.literal):
The context for the reference.
Returns:
list:
The list of any reference nodes, as created by intersphinx.
"""
orig_target = node['reftarget']
target = orig_target
domain = node.get('refdomain')
# See if we're referencing a std:option. Sphinx (as of 1.6.1) does not
# properly link these. A pull request has been opened to fix this
# (https://github.com/sphinx-doc/sphinx/pull/3769). Until we can make
# use of that, we're including the workaround here.
if domain == 'std' and node['reftype'] == 'option':
# Options are stored in the inventory as "program-name.--option".
# In order to look up the option, the target will need to be
# converted to this format.
#
# Ideally, we'd be able to utilize the same logic as found in
# StandardDomain._resolve_option_xref, but we don't have any
# information on the progoptions data stored there. Instead, we
# try to determine if the target already has a program name or
# an intersphinx doc set and normalize the contents to match the
# option reference name format.
i = target.rfind(' ')
if i != -1:
# The option target contains a program name and an option
# name. We can easily normalize this to be in
# <progname>.<option> format.
target = '%s.%s' % (target[:i], target[i + 1:])
target = target.replace(' ', '-')
else:
# Since a space was not found, and a program name is needed
# to complete the reference, we'll see if a ".. program::"
# has been set in this file. If so, we'll put that into the
# target name (being careful to consider any intersphinx doc
# set name that may be prefixed).
progname = node.get('std:program')
if progname:
if ':' in target:
setname, newtarget = target.split(':', 1)
target = '%s:%s.%s' % (setname, progname, newtarget)
else:
target = '%s.%s' % (progname, target)
if ':' not in target:
prefixes = \
env.metadata[env.docname].get('default-intersphinx-prefixes')
if prefixes:
# Try all supported prefixes in order. These are the only allowed
# to be inferred.
for prefix in prefixes:
old_content = contnode[0]
node['reftarget'] = '%s:%s' % (prefix, target)
result = intersphinx.missing_reference(app, env, node,
contnode)
if result:
return result
# Couldn't find it. Go back to the original target and try
# again.
node['reftarget'] = orig_target
contnode[0] = old_content
return None
return intersphinx.missing_reference(app, env, node, contnode)
def setup(app):
"""Set up the Sphinx extension.
This listens for the events needed to handle missing references, and
registers directives.
Args:
app (sphinx.application.Sphinx):
The Sphinx application building the docs.
"""
app.add_directive('default-intersphinx', DefaultIntersphinx)
# Disconnect the other intersphinx listener. We're going to override it.
listeners = app.events.listeners.get('missing-reference', {})
intersphinx_listener_id = None
for listener in listeners:
if listener.handler == intersphinx.missing_reference:
intersphinx_listener_id = listener.id
break
if intersphinx_listener_id is not None:
app.events.disconnect(intersphinx_listener_id)
else:
raise ExtensionError(
'beanbag_docutils.sphinx.ext.intersphinx_utils '
'must come after sphinx.ext.intersphinx')
app.connect('missing-reference', _on_missing_reference)
|